mirror of
https://github.com/jcwimer/wrestlingApp
synced 2026-03-25 01:14:43 +00:00
Compare commits
10 Commits
ae8d995b2c
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| 7526148ba5 | |||
| e8e0fa291b | |||
| 679fc2fcb9 | |||
| 18d39c6c8f | |||
| ca4d5ce0db | |||
| 654cb84827 | |||
| dc50efe8fc | |||
| 8670ce38c3 | |||
| d359be3ea1 | |||
| e97aa0d680 |
14
Gemfile.lock
14
Gemfile.lock
@@ -79,7 +79,7 @@ GEM
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.21)
|
||||
bigdecimal (4.0.1)
|
||||
bootsnap (1.22.0)
|
||||
bootsnap (1.23.0)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (8.0.2)
|
||||
racc
|
||||
@@ -157,12 +157,12 @@ GEM
|
||||
railties (>= 7.1)
|
||||
stimulus-rails
|
||||
turbo-rails
|
||||
mocha (3.0.1)
|
||||
mocha (3.0.2)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
msgpack (1.8.0)
|
||||
mysql2 (0.5.7)
|
||||
bigdecimal
|
||||
net-imap (0.6.2)
|
||||
net-imap (0.6.3)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -257,7 +257,7 @@ GEM
|
||||
rainbow (3.1.1)
|
||||
rake (13.3.1)
|
||||
rb-readline (0.5.5)
|
||||
rdoc (7.1.0)
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
@@ -269,7 +269,7 @@ GEM
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 2.0)
|
||||
rqrcode_core (2.1.0)
|
||||
rubocop (1.84.1)
|
||||
rubocop (1.84.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -384,7 +384,7 @@ DEPENDENCIES
|
||||
tzinfo-data
|
||||
|
||||
RUBY VERSION
|
||||
ruby 4.0.1p0
|
||||
ruby 4.0.1p0
|
||||
|
||||
BUNDLED WITH
|
||||
2.6.9
|
||||
4.0.3
|
||||
|
||||
@@ -5,7 +5,6 @@ import "@hotwired/turbo-rails";
|
||||
import { createConsumer } from "@rails/actioncable"; // Import createConsumer directly
|
||||
import "jquery";
|
||||
import "bootstrap";
|
||||
import "datatables.net";
|
||||
|
||||
// Stimulus setup
|
||||
import { Application } from "@hotwired/stimulus";
|
||||
@@ -20,12 +19,14 @@ import WrestlerColorController from "controllers/wrestler_color_controller";
|
||||
import MatchScoreController from "controllers/match_score_controller";
|
||||
import MatchDataController from "controllers/match_data_controller";
|
||||
import MatchSpectateController from "controllers/match_spectate_controller";
|
||||
import UpMatchesConnectionController from "controllers/up_matches_connection_controller";
|
||||
|
||||
// Register controllers
|
||||
application.register("wrestler-color", WrestlerColorController);
|
||||
application.register("match-score", MatchScoreController);
|
||||
application.register("match-data", MatchDataController);
|
||||
application.register("match-spectate", MatchSpectateController);
|
||||
application.register("up-matches-connection", UpMatchesConnectionController);
|
||||
|
||||
// Your existing Action Cable consumer setup
|
||||
(function() {
|
||||
@@ -39,7 +40,7 @@ application.register("match-spectate", MatchSpectateController);
|
||||
}
|
||||
}).call(this);
|
||||
|
||||
console.log("Propshaft/Importmap application.js initialized with jQuery, Bootstrap, Stimulus, and DataTables.");
|
||||
console.log("Propshaft/Importmap application.js initialized with jQuery, Bootstrap, and Stimulus.");
|
||||
|
||||
// If you have custom JavaScript files in app/javascript/ that were previously
|
||||
// handled by Sprockets `require_tree`, you'll need to import them here explicitly.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["stream", "statusIndicator"]
|
||||
|
||||
connect() {
|
||||
this.setupSubscription()
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.cleanupSubscription()
|
||||
}
|
||||
|
||||
setupSubscription() {
|
||||
this.cleanupSubscription()
|
||||
this.setStatus("Connecting to server for real-time bout board updates...", "info")
|
||||
|
||||
if (!this.hasStreamTarget) {
|
||||
this.setStatus("Error: Stream source not found.", "danger")
|
||||
return
|
||||
}
|
||||
|
||||
const signedStreamName = this.streamTarget.getAttribute("signed-stream-name")
|
||||
if (!signedStreamName) {
|
||||
this.setStatus("Error: Invalid stream source.", "danger")
|
||||
return
|
||||
}
|
||||
|
||||
if (!window.App || !window.App.cable) {
|
||||
this.setStatus("Error: WebSockets unavailable. Bout board won't update in real-time. Refresh the page to update.", "danger")
|
||||
return
|
||||
}
|
||||
|
||||
this.subscription = App.cable.subscriptions.create(
|
||||
{
|
||||
channel: "Turbo::StreamsChannel",
|
||||
signed_stream_name: signedStreamName
|
||||
},
|
||||
{
|
||||
connected: () => {
|
||||
this.setStatus("Connected: Bout board updating in real-time.", "success")
|
||||
},
|
||||
disconnected: () => {
|
||||
this.setStatus("Disconnected: Live bout board updates paused.", "warning")
|
||||
},
|
||||
rejected: () => {
|
||||
this.setStatus("Error: Live bout board connection rejected.", "danger")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
cleanupSubscription() {
|
||||
if (!this.subscription) return
|
||||
this.subscription.unsubscribe()
|
||||
this.subscription = null
|
||||
}
|
||||
|
||||
setStatus(message, type) {
|
||||
if (!this.hasStatusIndicatorTarget) return
|
||||
|
||||
this.statusIndicatorTarget.innerText = message
|
||||
this.statusIndicatorTarget.classList.remove("alert-secondary", "alert-info", "alert-success", "alert-warning", "alert-danger")
|
||||
|
||||
if (type === "success") this.statusIndicatorTarget.classList.add("alert-success")
|
||||
else if (type === "warning") this.statusIndicatorTarget.classList.add("alert-warning")
|
||||
else if (type === "danger") this.statusIndicatorTarget.classList.add("alert-danger")
|
||||
else this.statusIndicatorTarget.classList.add("alert-info")
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ class MatAssignmentRulesController < ApplicationController
|
||||
before_action :set_mat_assignment_rule, only: [:edit, :update, :destroy]
|
||||
|
||||
def index
|
||||
@mat_assignment_rules = @tournament.mat_assignment_rules
|
||||
@mat_assignment_rules = @tournament.mat_assignment_rules.includes(:mat)
|
||||
@weights_by_id = @tournament.weights.index_by(&:id) # For quick lookup
|
||||
end
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
class MatsController < ApplicationController
|
||||
before_action :set_mat, only: [:show, :edit, :update, :destroy, :assign_next_match]
|
||||
before_action :check_access, only: [:new,:create,:update,:destroy,:edit,:show, :assign_next_match]
|
||||
before_action :check_for_matches, only: [:show]
|
||||
|
||||
# GET /mats/1
|
||||
# GET /mats/1.json
|
||||
def show
|
||||
bout_number_param = params[:bout_number] # Read the bout_number from the URL params
|
||||
|
||||
if bout_number_param
|
||||
@show_next_bout_button = false
|
||||
@match = @mat.queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i }
|
||||
bout_number_param = params[:bout_number]
|
||||
@queue_matches = @mat.queue_matches
|
||||
@match = if bout_number_param
|
||||
@queue_matches.compact.find { |m| m.bout_number == bout_number_param.to_i }
|
||||
else
|
||||
@show_next_bout_button = true
|
||||
@match = @mat.queue1_match
|
||||
@queue_matches[0]
|
||||
end
|
||||
|
||||
@next_match = @mat.queue2_match # Second match on the mat
|
||||
# If a requested bout is no longer queued, fall back to queue1.
|
||||
@match ||= @queue_matches[0]
|
||||
@next_match = @queue_matches[1]
|
||||
@show_next_bout_button = false
|
||||
|
||||
@wrestlers = []
|
||||
if @match
|
||||
@@ -142,11 +141,4 @@ class MatsController < ApplicationController
|
||||
end
|
||||
|
||||
|
||||
def check_for_matches
|
||||
if @mat
|
||||
if @mat.tournament.matches.empty?
|
||||
redirect_to "/tournaments/#{@tournament.id}/no_matches"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
class StaticPagesController < ApplicationController
|
||||
|
||||
def my_tournaments
|
||||
tournaments_created = current_user.tournaments
|
||||
tournaments_delegated = current_user.delegated_tournaments
|
||||
tournaments_created = current_user.tournaments.to_a
|
||||
tournaments_delegated = current_user.delegated_tournaments.to_a
|
||||
all_tournaments = tournaments_created + tournaments_delegated
|
||||
@tournaments = all_tournaments.sort_by{|t| t.days_until_start}
|
||||
@schools = current_user.delegated_schools
|
||||
@schools = current_user.delegated_schools.includes(:tournament)
|
||||
end
|
||||
|
||||
def not_allowed
|
||||
|
||||
@@ -3,11 +3,11 @@ class TournamentsController < ApplicationController
|
||||
before_action :check_access_manage, only: [:delete_school_keys, :generate_school_keys,:reset_bout_board,:calculate_team_scores,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches,:qrcode]
|
||||
before_action :check_access_destroy, only: [:destroy,:delegate,:remove_delegate]
|
||||
before_action :check_tournament_errors, only: [:generate_matches]
|
||||
before_action :check_for_matches, only: [:all_results,:up_matches,:bracket,:all_brackets]
|
||||
before_action :check_for_matches, only: [:all_results,:bracket,:all_brackets]
|
||||
before_action :check_access_read, only: [:all_results,:up_matches,:bracket,:all_brackets]
|
||||
|
||||
def weigh_in_sheet
|
||||
|
||||
@schools = @tournament.schools.includes(wrestlers: :weight)
|
||||
end
|
||||
|
||||
def calculate_team_scores
|
||||
@@ -92,12 +92,9 @@ class TournamentsController < ApplicationController
|
||||
end
|
||||
end
|
||||
end
|
||||
@users_delegates = []
|
||||
@tournament.schools.each do |s|
|
||||
s.delegates.each do |d|
|
||||
@users_delegates << d
|
||||
end
|
||||
end
|
||||
@users_delegates = SchoolDelegate.includes(:user, :school)
|
||||
.joins(:school)
|
||||
.where(schools: { tournament_id: @tournament.id })
|
||||
end
|
||||
|
||||
def delegate
|
||||
@@ -115,11 +112,63 @@ class TournamentsController < ApplicationController
|
||||
end
|
||||
end
|
||||
end
|
||||
@users_delegates = @tournament.delegates
|
||||
@users_delegates = @tournament.delegates.includes(:user)
|
||||
end
|
||||
|
||||
def matches
|
||||
@matches = @tournament.matches.includes(:wrestlers,:schools).sort_by{|m| m.bout_number}
|
||||
per_page = 50
|
||||
@page = params[:page].to_i > 0 ? params[:page].to_i : 1
|
||||
offset = (@page - 1) * per_page
|
||||
matches_table = Match.arel_table
|
||||
|
||||
matches_scope = @tournament.matches.order(:bout_number)
|
||||
|
||||
if params[:search].present?
|
||||
wrestlers_table = Wrestler.arel_table
|
||||
schools_table = School.arel_table
|
||||
search_terms = params[:search].downcase.split
|
||||
|
||||
search_terms.each do |term|
|
||||
escaped_term = ActiveRecord::Base.sanitize_sql_like(term)
|
||||
pattern = "%#{escaped_term}%"
|
||||
|
||||
matching_wrestler_ids = Wrestler
|
||||
.joins(:weight)
|
||||
.left_outer_joins(:school)
|
||||
.where(weights: { tournament_id: @tournament.id })
|
||||
.where(
|
||||
wrestlers_table[:name].matches(pattern)
|
||||
.or(schools_table[:name].matches(pattern))
|
||||
)
|
||||
.distinct
|
||||
.select(:id)
|
||||
|
||||
term_scope = @tournament.matches.where(w1: matching_wrestler_ids)
|
||||
.or(@tournament.matches.where(w2: matching_wrestler_ids))
|
||||
|
||||
if term.match?(/\A\d+\z/)
|
||||
term_scope = term_scope.or(@tournament.matches.where(bout_number: term.to_i))
|
||||
end
|
||||
|
||||
matches_scope = matches_scope.where(id: term_scope.select(:id))
|
||||
end
|
||||
end
|
||||
|
||||
@total_count = matches_scope.count
|
||||
@total_pages = (@total_count / per_page.to_f).ceil
|
||||
@per_page = per_page
|
||||
|
||||
loser1_not_bye = matches_table[:loser1_name].not_eq("BYE").or(matches_table[:loser1_name].eq(nil))
|
||||
loser2_not_bye = matches_table[:loser2_name].not_eq("BYE").or(matches_table[:loser2_name].eq(nil))
|
||||
|
||||
non_bye_scope = matches_scope.where(loser1_not_bye).where(loser2_not_bye)
|
||||
@matches_without_byes_count = non_bye_scope.count
|
||||
@unfinished_matches_without_byes_count = non_bye_scope.where(finished: [nil, 0]).count
|
||||
|
||||
@matches = matches_scope
|
||||
.includes({ wrestler1: :school }, { wrestler2: :school }, { weight: :matches })
|
||||
.offset(offset)
|
||||
.limit(per_page)
|
||||
if @match
|
||||
@w1 = @match.wrestler1
|
||||
@w2 = @match.wrestler2
|
||||
@@ -129,10 +178,18 @@ class TournamentsController < ApplicationController
|
||||
|
||||
def weigh_in_weight
|
||||
if params[:wrestler]
|
||||
Wrestler.update(params[:wrestler].keys, params[:wrestler].values)
|
||||
sanitized_wrestlers = params.require(:wrestler).to_unsafe_h.each_with_object({}) do |(wrestler_id, attributes), result|
|
||||
permitted = ActionController::Parameters.new(attributes).permit(:offical_weight)
|
||||
result[wrestler_id] = permitted
|
||||
end
|
||||
Wrestler.update(sanitized_wrestlers.keys, sanitized_wrestlers.values) if sanitized_wrestlers.present?
|
||||
redirect_to "/tournaments/#{@tournament.id}/weigh_in/#{params[:weight]}", notice: "Weights were successfully recorded."
|
||||
return
|
||||
end
|
||||
if params[:weight]
|
||||
@weight = Weight.where(:id => params[:weight]).includes(:wrestlers).first
|
||||
@weight = Weight.where(id: params[:weight])
|
||||
.includes(wrestlers: [:school, :weight])
|
||||
.first
|
||||
@tournament_id = @tournament.id
|
||||
@tournament_name = @tournament.name
|
||||
@weights = @tournament.weights
|
||||
@@ -159,8 +216,11 @@ class TournamentsController < ApplicationController
|
||||
def all_brackets
|
||||
@schools = @tournament.schools
|
||||
@schools = @schools.sort_by{|s| s.page_score_string}.reverse!
|
||||
@matches = @tournament.matches.includes(:wrestlers,:schools)
|
||||
@weights = @tournament.weights.includes(:matches,:wrestlers)
|
||||
@weights = @tournament.weights.includes(:matches, wrestlers: :school)
|
||||
all_matches = @tournament.matches.includes(:weight, { wrestler1: :school }, { wrestler2: :school })
|
||||
all_wrestlers = @tournament.wrestlers.includes(:school, :weight)
|
||||
@matches_by_weight_id = all_matches.group_by(&:weight_id)
|
||||
@wrestlers_by_weight_id = all_wrestlers.group_by(&:weight_id)
|
||||
end
|
||||
|
||||
def bracket
|
||||
@@ -203,25 +263,30 @@ class TournamentsController < ApplicationController
|
||||
|
||||
|
||||
def up_matches
|
||||
# .where.not(loser1_name: 'BYE') won't return matches with NULL loser1_name
|
||||
# so I was only getting back matches with Loser of BOUT_NUMBER
|
||||
@matches = @tournament.matches
|
||||
.where("mat_id is NULL and (finished != 1 or finished is NULL)")
|
||||
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
|
||||
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
|
||||
.order('bout_number ASC')
|
||||
.limit(10).includes(:wrestlers)
|
||||
@mats = @tournament.mats.includes(:matches)
|
||||
@matches = @tournament.up_matches_unassigned_matches
|
||||
@mats = @tournament.up_matches_mats
|
||||
end
|
||||
|
||||
def bout_sheets
|
||||
matches_scope = @tournament.matches
|
||||
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
|
||||
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
|
||||
|
||||
if params[:round]
|
||||
round = params[:round]
|
||||
if round != "All"
|
||||
@matches = @tournament.matches.where("round = ?",round).sort_by{|match| match.bout_number}
|
||||
@matches = matches_scope
|
||||
.where(round: round)
|
||||
.includes(:weight)
|
||||
.order(:bout_number)
|
||||
else
|
||||
@matches = @tournament.matches.sort_by{|match| match.bout_number}
|
||||
@matches = matches_scope
|
||||
.includes(:weight)
|
||||
.order(:bout_number)
|
||||
end
|
||||
|
||||
wrestler_ids = @matches.flat_map { |match| [match.w1, match.w2] }.compact.uniq
|
||||
@wrestlers_by_id = Wrestler.includes(:school).where(id: wrestler_ids).index_by(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ class Mat < ApplicationRecord
|
||||
|
||||
QUEUE_SLOTS = %w[queue1 queue2 queue3 queue4].freeze
|
||||
|
||||
after_save :clear_queue_matches_cache
|
||||
after_commit :broadcast_up_matches_board, on: :update, if: :up_matches_queue_changed?
|
||||
|
||||
def assign_next_match
|
||||
slot = first_empty_queue_slot
|
||||
return true unless slot
|
||||
@@ -86,8 +89,22 @@ class Mat < ApplicationRecord
|
||||
QUEUE_SLOTS.map { |slot| public_send(slot) }
|
||||
end
|
||||
|
||||
# used to prevent N+1 query on each mat
|
||||
def queue_matches
|
||||
queue_match_ids.map { |match_id| match_id ? Match.find_by(id: match_id) : nil }
|
||||
slot_ids = queue_match_ids
|
||||
if @queue_matches.nil? || @queue_match_slot_ids != slot_ids
|
||||
ids = slot_ids.compact
|
||||
@queue_matches = if ids.empty?
|
||||
[nil, nil, nil, nil]
|
||||
else
|
||||
matches_by_id = Match.where(id: ids)
|
||||
.includes({ wrestler1: :school }, { wrestler2: :school }, { weight: :matches })
|
||||
.index_by(&:id)
|
||||
slot_ids.map { |match_id| match_id ? matches_by_id[match_id] : nil }
|
||||
end
|
||||
@queue_match_slot_ids = slot_ids
|
||||
end
|
||||
@queue_matches
|
||||
end
|
||||
|
||||
def queue1_match
|
||||
@@ -167,6 +184,7 @@ class Mat < ApplicationRecord
|
||||
|
||||
def clear_queue!
|
||||
update!(queue1: nil, queue2: nil, queue3: nil, queue4: nil)
|
||||
broadcast_current_match
|
||||
end
|
||||
|
||||
def unfinished_matches
|
||||
@@ -175,9 +193,13 @@ class Mat < ApplicationRecord
|
||||
|
||||
private
|
||||
|
||||
def clear_queue_matches_cache
|
||||
@queue_matches = nil
|
||||
@queue_match_slot_ids = nil
|
||||
end
|
||||
|
||||
def queue_match_at(position)
|
||||
match_id = public_send("queue#{position}")
|
||||
match_id ? Match.find_by(id: match_id) : nil
|
||||
queue_matches[position - 1]
|
||||
end
|
||||
|
||||
def first_empty_queue_slot
|
||||
@@ -256,4 +278,12 @@ class Mat < ApplicationRecord
|
||||
)
|
||||
end
|
||||
|
||||
def broadcast_up_matches_board
|
||||
Tournament.broadcast_up_matches_board(tournament_id)
|
||||
end
|
||||
|
||||
def up_matches_queue_changed?
|
||||
saved_change_to_queue1? || saved_change_to_queue2? || saved_change_to_queue3? || saved_change_to_queue4?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -5,6 +5,8 @@ class Match < ApplicationRecord
|
||||
belongs_to :weight, touch: true
|
||||
belongs_to :mat, touch: true, optional: true
|
||||
belongs_to :winner, class_name: 'Wrestler', foreign_key: 'winner_id', optional: true
|
||||
belongs_to :wrestler1, class_name: 'Wrestler', foreign_key: 'w1', optional: true
|
||||
belongs_to :wrestler2, class_name: 'Wrestler', foreign_key: 'w2', optional: true
|
||||
has_many :wrestlers, :through => :weight
|
||||
has_many :schools, :through => :wrestlers
|
||||
validate :score_validation, :win_type_validation, :bracket_position_validation, :overtime_type_validation
|
||||
@@ -15,6 +17,7 @@ class Match < ApplicationRecord
|
||||
# update mat show with correct match if bout board is reset
|
||||
# this is done with a turbo stream
|
||||
after_commit :broadcast_mat_assignment_change, if: :saved_change_to_mat_id?, on: [:create, :update]
|
||||
after_commit :broadcast_up_matches_board, on: :update, if: :saved_change_to_mat_id?
|
||||
|
||||
# Enqueue advancement and related actions after the DB transaction has committed.
|
||||
# Using after_commit ensures any background jobs enqueued inside these callbacks
|
||||
@@ -178,14 +181,6 @@ class Match < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def wrestler1
|
||||
wrestlers.select{|w| w.id == self.w1}.first
|
||||
end
|
||||
|
||||
def wrestler2
|
||||
wrestlers.select{|w| w.id == self.w2}.first
|
||||
end
|
||||
|
||||
def w1_name
|
||||
if self.w1 != nil
|
||||
wrestler1.name
|
||||
@@ -203,7 +198,7 @@ class Match < ApplicationRecord
|
||||
end
|
||||
|
||||
def w1_bracket_name
|
||||
first_round = self.weight.matches.sort_by{|m| m.round}.first.round
|
||||
first_round = first_round_for_weight
|
||||
return_string = ""
|
||||
return_string_ending = ""
|
||||
if self.w1 and self.winner_id == self.w1
|
||||
@@ -223,7 +218,7 @@ class Match < ApplicationRecord
|
||||
end
|
||||
|
||||
def w2_bracket_name
|
||||
first_round = self.weight.matches.sort_by{|m| m.round}.first.round
|
||||
first_round = first_round_for_weight
|
||||
return_string = ""
|
||||
return_string_ending = ""
|
||||
if self.w2 and self.winner_id == self.w2
|
||||
@@ -289,6 +284,17 @@ class Match < ApplicationRecord
|
||||
self.weight.max
|
||||
end
|
||||
|
||||
def first_round_for_weight
|
||||
return @first_round_for_weight if defined?(@first_round_for_weight)
|
||||
|
||||
@first_round_for_weight =
|
||||
if association(:weight).loaded? && self.weight&.association(:matches)&.loaded?
|
||||
self.weight.matches.map(&:round).compact.min
|
||||
else
|
||||
Match.where(weight_id: self.weight_id).minimum(:round)
|
||||
end
|
||||
end
|
||||
|
||||
def replace_loser_name_with_wrestler(w,loser_name)
|
||||
if self.loser1_name == loser_name
|
||||
self.w1 = w.id
|
||||
@@ -366,4 +372,8 @@ class Match < ApplicationRecord
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def broadcast_up_matches_board
|
||||
Tournament.broadcast_up_matches_board(tournament_id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,8 +69,35 @@ class Tournament < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def up_matches_unassigned_matches
|
||||
matches
|
||||
.where("mat_id is NULL and (finished != 1 or finished is NULL)")
|
||||
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
|
||||
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
|
||||
.order("bout_number ASC")
|
||||
.limit(10)
|
||||
.includes({ wrestler1: :school }, { wrestler2: :school }, { weight: :matches })
|
||||
end
|
||||
|
||||
def up_matches_mats
|
||||
mats.includes(:matches)
|
||||
end
|
||||
|
||||
def self.broadcast_up_matches_board(tournament_id)
|
||||
tournament = find_by(id: tournament_id)
|
||||
return unless tournament
|
||||
|
||||
Turbo::StreamsChannel.broadcast_replace_to(
|
||||
tournament,
|
||||
target: "up_matches_board",
|
||||
partial: "tournaments/up_matches_board",
|
||||
locals: { tournament: tournament }
|
||||
)
|
||||
end
|
||||
|
||||
def destroy_all_matches
|
||||
matches.destroy_all
|
||||
mats.each(&:clear_queue!)
|
||||
end
|
||||
|
||||
def matches_by_round(round)
|
||||
@@ -97,18 +124,11 @@ class Tournament < ApplicationRecord
|
||||
end
|
||||
|
||||
def pointAdjustments
|
||||
point_adjustments = []
|
||||
self.schools.each do |s|
|
||||
s.deductedPoints.each do |d|
|
||||
point_adjustments << d
|
||||
end
|
||||
end
|
||||
self.wrestlers.each do |w|
|
||||
w.deductedPoints.each do |d|
|
||||
point_adjustments << d
|
||||
end
|
||||
end
|
||||
point_adjustments
|
||||
school_scope = Teampointadjust.where(school_id: schools.select(:id))
|
||||
wrestler_scope = Teampointadjust.where(wrestler_id: wrestlers.select(:id))
|
||||
|
||||
Teampointadjust.includes(:school, :wrestler)
|
||||
.merge(school_scope.or(wrestler_scope))
|
||||
end
|
||||
|
||||
def remove_school_delegations
|
||||
|
||||
@@ -53,19 +53,16 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def delegated_tournaments
|
||||
tournaments_delegated = []
|
||||
delegated_tournament_permissions.each do |t|
|
||||
tournaments_delegated << t.tournament
|
||||
end
|
||||
tournaments_delegated
|
||||
Tournament.joins(:delegates)
|
||||
.where(tournament_delegates: { user_id: id })
|
||||
.distinct
|
||||
end
|
||||
|
||||
def delegated_schools
|
||||
schools_delegated = []
|
||||
delegated_school_permissions.each do |t|
|
||||
schools_delegated << t.school
|
||||
end
|
||||
schools_delegated
|
||||
School.joins(:delegates)
|
||||
.where(school_delegates: { user_id: id })
|
||||
.includes(:tournament)
|
||||
.distinct
|
||||
end
|
||||
|
||||
def self.search(search)
|
||||
|
||||
@@ -156,7 +156,7 @@ class Weight < ApplicationRecord
|
||||
end
|
||||
|
||||
def calculate_bracket_size
|
||||
num_wrestlers = wrestlers.reload.size
|
||||
num_wrestlers = wrestlers.size
|
||||
return nil if num_wrestlers <= 0 # Handle invalid input
|
||||
|
||||
# Find the smallest power of 2 greater than or equal to num_wrestlers
|
||||
|
||||
@@ -12,21 +12,97 @@ class AdvanceWrestler
|
||||
end
|
||||
|
||||
def advance_raw
|
||||
@last_match.reload
|
||||
@wrestler.reload
|
||||
if @last_match && @last_match.finished?
|
||||
pool_to_bracket_advancement if @tournament.tournament_type == "Pool to bracket"
|
||||
ModifiedDoubleEliminationAdvance.new(@wrestler, @last_match).bracket_advancement if @tournament.tournament_type.include? "Modified 16 Man Double Elimination"
|
||||
DoubleEliminationAdvance.new(@wrestler, @last_match).bracket_advancement if @tournament.tournament_type.include? "Regular Double Elimination"
|
||||
@last_match = Match.find_by(id: @last_match&.id)
|
||||
@wrestler = Wrestler.includes(:school, :weight).find_by(id: @wrestler.id)
|
||||
return unless @last_match && @wrestler && @last_match.finished?
|
||||
|
||||
context = preload_advancement_context
|
||||
matches_to_advance = []
|
||||
|
||||
if @tournament.tournament_type == "Pool to bracket"
|
||||
matches_to_advance.concat(pool_to_bracket_advancement(context))
|
||||
elsif @tournament.tournament_type.include?("Modified 16 Man Double Elimination")
|
||||
service = ModifiedDoubleEliminationAdvance.new(@wrestler, @last_match, matches: context[:matches])
|
||||
service.bracket_advancement
|
||||
matches_to_advance.concat(service.matches_to_advance)
|
||||
elsif @tournament.tournament_type.include?("Regular Double Elimination")
|
||||
service = DoubleEliminationAdvance.new(@wrestler, @last_match, matches: context[:matches])
|
||||
service.bracket_advancement
|
||||
matches_to_advance.concat(service.matches_to_advance)
|
||||
end
|
||||
|
||||
persist_advancement_changes(context)
|
||||
advance_pending_matches(matches_to_advance)
|
||||
@wrestler.school.calculate_score
|
||||
end
|
||||
|
||||
def pool_to_bracket_advancement
|
||||
if @wrestler.weight.all_pool_matches_finished(@wrestler.pool) and (@wrestler.finished_bracket_matches.size < 1)
|
||||
PoolOrder.new(@wrestler.weight.wrestlers_in_pool(@wrestler.pool)).getPoolOrder
|
||||
def preload_advancement_context
|
||||
weight = Weight.includes(:matches, :wrestlers).find(@wrestler.weight_id)
|
||||
{
|
||||
weight: weight,
|
||||
matches: weight.matches.to_a,
|
||||
wrestlers: weight.wrestlers.to_a
|
||||
}
|
||||
end
|
||||
|
||||
def persist_advancement_changes(context)
|
||||
persist_matches(context[:matches])
|
||||
persist_wrestlers(context[:wrestlers])
|
||||
end
|
||||
|
||||
def persist_matches(matches)
|
||||
timestamp = Time.current
|
||||
updates = matches.filter_map do |m|
|
||||
next unless m.changed?
|
||||
|
||||
{
|
||||
id: m.id,
|
||||
w1: m.w1,
|
||||
w2: m.w2,
|
||||
winner_id: m.winner_id,
|
||||
win_type: m.win_type,
|
||||
score: m.score,
|
||||
finished: m.finished,
|
||||
loser1_name: m.loser1_name,
|
||||
loser2_name: m.loser2_name,
|
||||
finished_at: m.finished_at,
|
||||
updated_at: timestamp
|
||||
}
|
||||
end
|
||||
PoolAdvance.new(@wrestler).advanceWrestler
|
||||
Match.upsert_all(updates) if updates.any?
|
||||
end
|
||||
|
||||
def persist_wrestlers(wrestlers)
|
||||
timestamp = Time.current
|
||||
updates = wrestlers.filter_map do |w|
|
||||
next unless w.changed?
|
||||
|
||||
{
|
||||
id: w.id,
|
||||
pool_placement: w.pool_placement,
|
||||
pool_placement_tiebreaker: w.pool_placement_tiebreaker,
|
||||
updated_at: timestamp
|
||||
}
|
||||
end
|
||||
Wrestler.upsert_all(updates) if updates.any?
|
||||
end
|
||||
|
||||
def advance_pending_matches(matches_to_advance)
|
||||
matches_to_advance.uniq(&:id).each do |match|
|
||||
match.advance_wrestlers
|
||||
end
|
||||
end
|
||||
|
||||
def pool_to_bracket_advancement(context)
|
||||
matches_to_advance = []
|
||||
wrestlers_in_pool = context[:wrestlers].select { |w| w.pool == @wrestler.pool }
|
||||
if @wrestler.weight.all_pool_matches_finished(@wrestler.pool) && (@wrestler.finished_bracket_matches.size < 1)
|
||||
PoolOrder.new(wrestlers_in_pool).getPoolOrder
|
||||
end
|
||||
service = PoolAdvance.new(@wrestler, @last_match, matches: context[:matches], wrestlers: context[:wrestlers])
|
||||
service.advanceWrestler
|
||||
matches_to_advance.concat(service.matches_to_advance)
|
||||
matches_to_advance
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
class DoubleEliminationAdvance
|
||||
|
||||
def initialize(wrestler,last_match)
|
||||
attr_reader :matches_to_advance
|
||||
|
||||
def initialize(wrestler,last_match, matches: nil)
|
||||
@wrestler = wrestler
|
||||
@last_match = last_match
|
||||
@matches = matches || @wrestler.weight.matches.to_a
|
||||
@matches_to_advance = []
|
||||
@next_match_position_number = (@last_match.bracket_position_number / 2.0)
|
||||
end
|
||||
|
||||
@@ -48,7 +52,7 @@ class DoubleEliminationAdvance
|
||||
end
|
||||
|
||||
if next_match_bracket_position
|
||||
next_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?",next_match_bracket_position,next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
next_match = @matches.find { |m| m.bracket_position == next_match_bracket_position && m.bracket_position_number == next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
end
|
||||
|
||||
if next_match
|
||||
@@ -59,18 +63,16 @@ class DoubleEliminationAdvance
|
||||
def update_new_match(match, wrestler_number)
|
||||
if wrestler_number == 2 or (match.loser1_name and match.loser1_name.include? "Loser of")
|
||||
match.w2 = @wrestler.id
|
||||
match.save
|
||||
elsif wrestler_number == 1
|
||||
match.w1 = @wrestler.id
|
||||
match.save
|
||||
end
|
||||
end
|
||||
|
||||
def update_consolation_bye
|
||||
bout = @wrestler.last_match.bout_number
|
||||
next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?","Loser of #{bout}","Loser of #{bout}",@wrestler.weight_id)
|
||||
if next_match.size > 0
|
||||
next_match.first.replace_loser_name_with_bye("Loser of #{bout}")
|
||||
next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") }
|
||||
if next_match
|
||||
replace_loser_name_with_bye(next_match, "Loser of #{bout}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -84,27 +86,18 @@ class DoubleEliminationAdvance
|
||||
|
||||
def losers_bracket_advancement
|
||||
bout = @last_match.bout_number
|
||||
next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?", "Loser of #{bout}", "Loser of #{bout}", @wrestler.weight_id).first
|
||||
next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") }
|
||||
|
||||
if next_match.present?
|
||||
next_match.replace_loser_name_with_wrestler(@wrestler, "Loser of #{bout}")
|
||||
next_match.reload
|
||||
replace_loser_name_with_wrestler(next_match, @wrestler, "Loser of #{bout}")
|
||||
|
||||
if next_match.loser1_name == "BYE" || next_match.loser2_name == "BYE"
|
||||
next_match.winner_id = @wrestler.id
|
||||
next_match.win_type = "BYE"
|
||||
next_match.score = ""
|
||||
next_match.finished = 1
|
||||
# puts "Before save: winner_id=#{next_match.winner_id}"
|
||||
|
||||
# if next_match.save
|
||||
# puts "Save successful: winner_id=#{next_match.reload.winner_id}"
|
||||
# else
|
||||
# puts "Save failed: #{next_match.errors.full_messages}"
|
||||
# end
|
||||
next_match.save
|
||||
|
||||
next_match.advance_wrestlers
|
||||
next_match.finished_at = Time.current
|
||||
@matches_to_advance << next_match
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -112,51 +105,69 @@ class DoubleEliminationAdvance
|
||||
|
||||
def advance_double_byes
|
||||
weight = @wrestler.weight
|
||||
weight.matches.select{|m| m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match|
|
||||
@matches.select{|m| m.weight_id == weight.id && m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match|
|
||||
match.finished = 1
|
||||
match.finished_at = Time.current
|
||||
match.score = ""
|
||||
match.win_type = "BYE"
|
||||
next_match_position_number = (match.bracket_position_number / 2.0).ceil
|
||||
after_matches = weight.matches.select{|m| m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round}
|
||||
next_matches = weight.matches.select{|m| m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match }
|
||||
this_round_matches = weight.matches.select{|m| m.round == match.round and m.is_consolation_match == match.is_consolation_match }
|
||||
after_matches = @matches.select{|m| m.weight_id == weight.id && m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round}
|
||||
next if after_matches.empty?
|
||||
next_matches = @matches.select{|m| m.weight_id == weight.id && m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match }
|
||||
this_round_matches = @matches.select{|m| m.weight_id == weight.id && m.round == match.round and m.is_consolation_match == match.is_consolation_match }
|
||||
next_match = nil
|
||||
|
||||
if next_matches.size == this_round_matches.size
|
||||
next_match = next_matches.select{|m| m.bracket_position_number == match.bracket_position_number}.first
|
||||
next_match.loser2_name = "BYE"
|
||||
next_match.save
|
||||
next_match.loser2_name = "BYE" if next_match
|
||||
elsif next_matches.size < this_round_matches.size and next_matches.size > 0
|
||||
next_match = next_matches.select{|m| m.bracket_position_number == next_match_position_number}.first
|
||||
if next_match.bracket_position_number == next_match_position_number
|
||||
if next_match && next_match.bracket_position_number == next_match_position_number
|
||||
next_match.loser2_name = "BYE"
|
||||
else
|
||||
elsif next_match
|
||||
next_match.loser1_name = "BYE"
|
||||
end
|
||||
end
|
||||
next_match.save
|
||||
match.save
|
||||
end
|
||||
end
|
||||
|
||||
def set_bye_for_placement
|
||||
weight = @wrestler.weight
|
||||
fifth_finals = weight.matches.select{|match| match.bracket_position == '5/6'}.first
|
||||
seventh_finals = weight.matches.select{|match| match.bracket_position == '7/8'}.first
|
||||
fifth_finals = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == '5/6'}.first
|
||||
seventh_finals = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == '7/8'}.first
|
||||
if seventh_finals
|
||||
conso_quarter = weight.matches.select{|match| match.bracket_position == 'Conso Quarter'}
|
||||
conso_quarter = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == 'Conso Quarter'}
|
||||
conso_quarter.each do |match|
|
||||
if match.loser1_name == "BYE" or match.loser2_name == "BYE"
|
||||
seventh_finals.replace_loser_name_with_bye("Loser of #{match.bout_number}")
|
||||
replace_loser_name_with_bye(seventh_finals, "Loser of #{match.bout_number}")
|
||||
end
|
||||
end
|
||||
end
|
||||
if fifth_finals
|
||||
conso_semis = weight.matches.select{|match| match.bracket_position == 'Conso Semis'}
|
||||
conso_semis = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == 'Conso Semis'}
|
||||
conso_semis.each do |match|
|
||||
if match.loser1_name == "BYE" or match.loser2_name == "BYE"
|
||||
fifth_finals.replace_loser_name_with_bye("Loser of #{match.bout_number}")
|
||||
replace_loser_name_with_bye(fifth_finals, "Loser of #{match.bout_number}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def replace_loser_name_with_wrestler(match, wrestler, loser_name)
|
||||
if match.loser1_name == loser_name
|
||||
match.w1 = wrestler.id
|
||||
end
|
||||
if match.loser2_name == loser_name
|
||||
match.w2 = wrestler.id
|
||||
end
|
||||
end
|
||||
|
||||
def replace_loser_name_with_bye(match, loser_name)
|
||||
if match.loser1_name == loser_name
|
||||
match.loser1_name = "BYE"
|
||||
end
|
||||
if match.loser2_name == loser_name
|
||||
match.loser2_name = "BYE"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
class ModifiedDoubleEliminationAdvance
|
||||
|
||||
def initialize(wrestler,last_match)
|
||||
attr_reader :matches_to_advance
|
||||
|
||||
def initialize(wrestler,last_match, matches: nil)
|
||||
@wrestler = wrestler
|
||||
@last_match = last_match
|
||||
@matches = matches || @wrestler.weight.matches.to_a
|
||||
@matches_to_advance = []
|
||||
@next_match_position_number = (@last_match.bracket_position_number / 2.0)
|
||||
end
|
||||
|
||||
@@ -25,42 +29,41 @@ class ModifiedDoubleEliminationAdvance
|
||||
update_consolation_bye
|
||||
end
|
||||
if @last_match.bracket_position == "Quarter"
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","Semis",@next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "Semis" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
update_new_match(new_match, get_wrestler_number)
|
||||
elsif @last_match.bracket_position == "Semis"
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","1/2",@next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "1/2" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
update_new_match(new_match, get_wrestler_number)
|
||||
elsif @last_match.bracket_position == "Conso Semis"
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","5/6",@next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "5/6" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
update_new_match(new_match, get_wrestler_number)
|
||||
elsif @last_match.bracket_position == "Conso Quarter"
|
||||
# it's a special bracket where a semi loser is not dropping down
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","Conso Semis",@next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "Conso Semis" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
update_new_match(new_match, get_wrestler_number)
|
||||
elsif @last_match.bracket_position == "Bracket Round of 16"
|
||||
new_match = Match.where("bracket_position_number = ? and weight_id = ? and round > ? and bracket_position = ?", @next_match_position_number.ceil,@wrestler.weight_id, @last_match.round , "Quarter").sort_by{|m| m.round}.first
|
||||
new_match = @matches.select { |m| m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id && m.round > @last_match.round && m.bracket_position == "Quarter" }.sort_by(&:round).first
|
||||
update_new_match(new_match, get_wrestler_number)
|
||||
elsif @last_match.bracket_position == "Conso Round of 8"
|
||||
new_match = Match.where("bracket_position_number = ? and weight_id = ? and round > ? and bracket_position = ?", @last_match.bracket_position_number,@wrestler.weight_id, @last_match.round, "Conso Quarter").sort_by{|m| m.round}.first
|
||||
new_match = @matches.select { |m| m.bracket_position_number == @last_match.bracket_position_number && m.weight_id == @wrestler.weight_id && m.round > @last_match.round && m.bracket_position == "Conso Quarter" }.sort_by(&:round).first
|
||||
update_new_match(new_match, get_wrestler_number)
|
||||
end
|
||||
end
|
||||
|
||||
def update_new_match(match, wrestler_number)
|
||||
return unless match
|
||||
if wrestler_number == 2 or (match.loser1_name and match.loser1_name.include? "Loser of")
|
||||
match.w2 = @wrestler.id
|
||||
match.save
|
||||
elsif wrestler_number == 1
|
||||
match.w1 = @wrestler.id
|
||||
match.save
|
||||
end
|
||||
end
|
||||
|
||||
def update_consolation_bye
|
||||
bout = @wrestler.last_match.bout_number
|
||||
next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?","Loser of #{bout}","Loser of #{bout}",@wrestler.weight_id)
|
||||
if next_match.size > 0
|
||||
next_match.first.replace_loser_name_with_bye("Loser of #{bout}")
|
||||
next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") }
|
||||
if next_match
|
||||
replace_loser_name_with_bye(next_match, "Loser of #{bout}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,27 +77,18 @@ class ModifiedDoubleEliminationAdvance
|
||||
|
||||
def losers_bracket_advancement
|
||||
bout = @last_match.bout_number
|
||||
next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?", "Loser of #{bout}", "Loser of #{bout}", @wrestler.weight_id).first
|
||||
next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") }
|
||||
|
||||
if next_match.present?
|
||||
next_match.replace_loser_name_with_wrestler(@wrestler, "Loser of #{bout}")
|
||||
next_match.reload
|
||||
replace_loser_name_with_wrestler(next_match, @wrestler, "Loser of #{bout}")
|
||||
|
||||
if next_match.loser1_name == "BYE" || next_match.loser2_name == "BYE"
|
||||
next_match.winner_id = @wrestler.id
|
||||
next_match.win_type = "BYE"
|
||||
next_match.score = ""
|
||||
next_match.finished = 1
|
||||
# puts "Before save: winner_id=#{next_match.winner_id}"
|
||||
|
||||
# if next_match.save
|
||||
# puts "Save successful: winner_id=#{next_match.reload.winner_id}"
|
||||
# else
|
||||
# puts "Save failed: #{next_match.errors.full_messages}"
|
||||
# end
|
||||
next_match.save
|
||||
|
||||
next_match.advance_wrestlers
|
||||
next_match.finished_at = Time.current
|
||||
@matches_to_advance << next_match
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -102,43 +96,53 @@ class ModifiedDoubleEliminationAdvance
|
||||
|
||||
def advance_double_byes
|
||||
weight = @wrestler.weight
|
||||
weight.matches.select{|m| m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match|
|
||||
@matches.select{|m| m.weight_id == weight.id && m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match|
|
||||
match.finished = 1
|
||||
match.finished_at = Time.current
|
||||
match.score = ""
|
||||
match.win_type = "BYE"
|
||||
next_match_position_number = (match.bracket_position_number / 2.0).ceil
|
||||
after_matches = weight.matches.select{|m| m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round}
|
||||
next_matches = weight.matches.select{|m| m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match }
|
||||
this_round_matches = weight.matches.select{|m| m.round == match.round and m.is_consolation_match == match.is_consolation_match }
|
||||
after_matches = @matches.select{|m| m.weight_id == weight.id && m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round}
|
||||
next if after_matches.empty?
|
||||
next_matches = @matches.select{|m| m.weight_id == weight.id && m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match }
|
||||
this_round_matches = @matches.select{|m| m.weight_id == weight.id && m.round == match.round and m.is_consolation_match == match.is_consolation_match }
|
||||
next_match = nil
|
||||
|
||||
if next_matches.size == this_round_matches.size
|
||||
next_match = next_matches.select{|m| m.bracket_position_number == match.bracket_position_number}.first
|
||||
next_match.loser2_name = "BYE"
|
||||
next_match.save
|
||||
next_match.loser2_name = "BYE" if next_match
|
||||
elsif next_matches.size < this_round_matches.size and next_matches.size > 0
|
||||
next_match = next_matches.select{|m| m.bracket_position_number == next_match_position_number}.first
|
||||
if next_match.bracket_position_number == next_match_position_number
|
||||
if next_match && next_match.bracket_position_number == next_match_position_number
|
||||
next_match.loser2_name = "BYE"
|
||||
else
|
||||
elsif next_match
|
||||
next_match.loser1_name = "BYE"
|
||||
end
|
||||
end
|
||||
next_match.save
|
||||
match.save
|
||||
end
|
||||
end
|
||||
|
||||
def set_bye_for_placement
|
||||
weight = @wrestler.weight
|
||||
seventh_finals = weight.matches.select{|match| match.bracket_position == '7/8'}.first
|
||||
seventh_finals = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == '7/8'}.first
|
||||
if seventh_finals
|
||||
conso_quarter = weight.matches.select{|match| match.bracket_position == 'Conso Semis'}
|
||||
conso_quarter = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == 'Conso Semis'}
|
||||
conso_quarter.each do |match|
|
||||
if match.loser1_name == "BYE" or match.loser2_name == "BYE"
|
||||
seventh_finals.replace_loser_name_with_bye("Loser of #{match.bout_number}")
|
||||
replace_loser_name_with_bye(seventh_finals, "Loser of #{match.bout_number}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def replace_loser_name_with_wrestler(match, wrestler, loser_name)
|
||||
match.w1 = wrestler.id if match.loser1_name == loser_name
|
||||
match.w2 = wrestler.id if match.loser2_name == loser_name
|
||||
end
|
||||
|
||||
def replace_loser_name_with_bye(match, loser_name)
|
||||
match.loser1_name = "BYE" if match.loser1_name == loser_name
|
||||
match.loser2_name = "BYE" if match.loser2_name == loser_name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
class PoolAdvance
|
||||
|
||||
def initialize(wrestler)
|
||||
attr_reader :matches_to_advance
|
||||
|
||||
def initialize(wrestler, last_match, matches: nil, wrestlers: nil)
|
||||
@wrestler = wrestler
|
||||
@last_match = @wrestler.last_match
|
||||
@last_match = last_match
|
||||
@matches = matches || @wrestler.weight.matches.to_a
|
||||
@wrestlers = wrestlers || @wrestler.weight.wrestlers.to_a
|
||||
@matches_to_advance = []
|
||||
end
|
||||
|
||||
def advanceWrestler
|
||||
@@ -17,15 +22,15 @@ class PoolAdvance
|
||||
def poolToBracketAdvancment
|
||||
pool = @wrestler.pool
|
||||
# This has to always run because the last match in a pool might not be a pool winner or runner up
|
||||
winner = Wrestler.where("weight_id = ? and pool_placement = 1 and pool = ?",@wrestler.weight.id, pool).first
|
||||
runner_up = Wrestler.where("weight_id = ? and pool_placement = 2 and pool = ?",@wrestler.weight.id, pool).first
|
||||
winner = @wrestlers.find { |w| w.weight_id == @wrestler.weight.id && w.pool_placement == 1 && w.pool == pool }
|
||||
runner_up = @wrestlers.find { |w| w.weight_id == @wrestler.weight.id && w.pool_placement == 2 && w.pool == pool }
|
||||
if runner_up
|
||||
runner_up_match = Match.where("weight_id = ? and (loser1_name = ? or loser2_name = ?)",@wrestler.weight.id, "Runner Up Pool #{pool}", "Runner Up Pool #{pool}").first
|
||||
runner_up_match.replace_loser_name_with_wrestler(runner_up,"Runner Up Pool #{pool}")
|
||||
runner_up_match = @matches.find { |m| m.weight_id == @wrestler.weight.id && (m.loser1_name == "Runner Up Pool #{pool}" || m.loser2_name == "Runner Up Pool #{pool}") }
|
||||
replace_loser_name_with_wrestler(runner_up_match, runner_up, "Runner Up Pool #{pool}") if runner_up_match
|
||||
end
|
||||
if winner
|
||||
winner_match = Match.where("weight_id = ? and (loser1_name = ? or loser2_name = ?)",@wrestler.weight.id, "Winner Pool #{pool}", "Winner Pool #{pool}").first
|
||||
winner_match.replace_loser_name_with_wrestler(winner,"Winner Pool #{pool}")
|
||||
winner_match = @matches.find { |m| m.weight_id == @wrestler.weight.id && (m.loser1_name == "Winner Pool #{pool}" || m.loser2_name == "Winner Pool #{pool}") }
|
||||
replace_loser_name_with_wrestler(winner_match, winner, "Winner Pool #{pool}") if winner_match
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,36 +50,40 @@ class PoolAdvance
|
||||
|
||||
def winner_advance
|
||||
if @wrestler.last_match.bracket_position == "Quarter"
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","Semis",@wrestler.next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "Semis" && m.bracket_position_number == @wrestler.next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
updateNewMatch(new_match)
|
||||
end
|
||||
if @wrestler.last_match.bracket_position == "Semis"
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","1/2",@wrestler.next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "1/2" && m.bracket_position_number == @wrestler.next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
updateNewMatch(new_match)
|
||||
end
|
||||
if @wrestler.last_match.bracket_position == "Conso Semis"
|
||||
new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","5/6",@wrestler.next_match_position_number.ceil,@wrestler.weight_id).first
|
||||
new_match = @matches.find { |m| m.bracket_position == "5/6" && m.bracket_position_number == @wrestler.next_match_position_number.ceil && m.weight_id == @wrestler.weight_id }
|
||||
updateNewMatch(new_match)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def updateNewMatch(match)
|
||||
return unless match
|
||||
if @wrestler.next_match_position_number == @wrestler.next_match_position_number.ceil
|
||||
match.w2 = @wrestler.id
|
||||
match.save
|
||||
end
|
||||
if @wrestler.next_match_position_number != @wrestler.next_match_position_number.ceil
|
||||
match.w1 = @wrestler.id
|
||||
match.save
|
||||
end
|
||||
end
|
||||
|
||||
def loser_advance
|
||||
bout = @wrestler.last_match.bout_number
|
||||
next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?","Loser of #{bout}","Loser of #{bout}",@wrestler.weight_id)
|
||||
if next_match.size > 0
|
||||
next_match.first.replace_loser_name_with_wrestler(@wrestler,"Loser of #{bout}")
|
||||
next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") }
|
||||
if next_match
|
||||
replace_loser_name_with_wrestler(next_match, @wrestler, "Loser of #{bout}")
|
||||
end
|
||||
end
|
||||
|
||||
def replace_loser_name_with_wrestler(match, wrestler, loser_name)
|
||||
match.w1 = wrestler.id if match.loser1_name == loser_name
|
||||
match.w2 = wrestler.id if match.loser2_name == loser_name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,8 +4,6 @@ class PoolOrder
|
||||
end
|
||||
|
||||
def getPoolOrder
|
||||
# clear caching for weight for bracket page
|
||||
@wrestlers.first.weight.touch
|
||||
setOriginalPoints
|
||||
while checkForTies(@wrestlers) == true
|
||||
getWrestlersOrderByPoolAdvancePoints.each do |wrestler|
|
||||
@@ -18,7 +16,6 @@ class PoolOrder
|
||||
getWrestlersOrderByPoolAdvancePoints.each_with_index do |wrestler, index|
|
||||
placement = index + 1
|
||||
wrestler.pool_placement = placement
|
||||
wrestler.save
|
||||
end
|
||||
@wrestlers.sort_by{|w| w.poolAdvancePoints}.reverse!
|
||||
end
|
||||
@@ -29,7 +26,6 @@ class PoolOrder
|
||||
|
||||
def setOriginalPoints
|
||||
@wrestlers.each do |w|
|
||||
matches = w.reload.all_matches
|
||||
w.pool_placement_tiebreaker = nil
|
||||
w.pool_placement = nil
|
||||
w.poolAdvancePoints = w.pool_wins.size
|
||||
|
||||
55
app/services/school_services/calculate_school_score.rb
Normal file
55
app/services/school_services/calculate_school_score.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
class CalculateSchoolScore
|
||||
def initialize(school)
|
||||
@school = school
|
||||
end
|
||||
|
||||
def calculate
|
||||
school = preload_school_context
|
||||
score = calculate_score_value(school)
|
||||
persist_school_score(school.id, score)
|
||||
score
|
||||
end
|
||||
|
||||
def calculate_value
|
||||
school = preload_school_context
|
||||
calculate_score_value(school)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def preload_school_context
|
||||
School
|
||||
.includes(
|
||||
:deductedPoints,
|
||||
wrestlers: [
|
||||
:deductedPoints,
|
||||
{ matches_as_w1: :winner },
|
||||
{ matches_as_w2: :winner },
|
||||
{ weight: [:matches, { tournament: { weights: :wrestlers } }] }
|
||||
]
|
||||
)
|
||||
.find(@school.id)
|
||||
end
|
||||
|
||||
def calculate_score_value(school)
|
||||
total_points_scored_by_wrestlers(school) - total_points_deducted(school)
|
||||
end
|
||||
|
||||
def total_points_scored_by_wrestlers(school)
|
||||
school.wrestlers.sum { |wrestler| CalculateWrestlerTeamScore.new(wrestler).totalScore }
|
||||
end
|
||||
|
||||
def total_points_deducted(school)
|
||||
school.deductedPoints.sum(&:points)
|
||||
end
|
||||
|
||||
def persist_school_score(school_id, score)
|
||||
School.upsert_all([
|
||||
{
|
||||
id: school_id,
|
||||
score: score,
|
||||
updated_at: Time.current
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
@@ -3,33 +3,30 @@ class DoubleEliminationGenerateLoserNames
|
||||
@tournament = tournament
|
||||
end
|
||||
|
||||
# Entry point: assign loser placeholders and advance any byes
|
||||
def assign_loser_names
|
||||
# Compatibility wrapper. Returns transformed rows and does not persist.
|
||||
def assign_loser_names(match_rows = nil)
|
||||
rows = match_rows || @tournament.matches.where(tournament_id: @tournament.id).map { |m| m.attributes.symbolize_keys }
|
||||
@tournament.weights.each do |weight|
|
||||
# only assign loser names if there's conso matches to be had
|
||||
if weight.calculate_bracket_size > 2
|
||||
assign_loser_names_for_weight(weight)
|
||||
advance_bye_matches_championship(weight)
|
||||
advance_bye_matches_consolation(weight)
|
||||
end
|
||||
next unless weight.calculate_bracket_size > 2
|
||||
|
||||
assign_loser_names_in_memory(weight, rows)
|
||||
assign_bye_outcomes_in_memory(weight, rows)
|
||||
end
|
||||
rows
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Assign loser names for a single weight bracket
|
||||
def assign_loser_names_for_weight(weight)
|
||||
def assign_loser_names_in_memory(weight, match_rows)
|
||||
bracket_size = weight.calculate_bracket_size
|
||||
matches = weight.matches.reload
|
||||
num_placers = @tournament.number_of_placers
|
||||
return if bracket_size <= 2
|
||||
|
||||
rows = match_rows.select { |row| row[:weight_id] == weight.id }
|
||||
num_placers = @tournament.number_of_placers
|
||||
|
||||
# Build dynamic round definitions
|
||||
champ_rounds = dynamic_championship_rounds(bracket_size)
|
||||
conso_rounds = dynamic_consolation_rounds(bracket_size)
|
||||
first_round = { bracket_position: first_round_label(bracket_size) }
|
||||
champ_full = [first_round] + champ_rounds
|
||||
first_round = { bracket_position: first_round_label(bracket_size) }
|
||||
champ_full = [first_round] + champ_rounds
|
||||
|
||||
# Map championship losers into consolation slots
|
||||
mappings = []
|
||||
champ_full[0...-1].each_with_index do |champ_info, i|
|
||||
map_idx = i.zero? ? 0 : (2 * i - 1)
|
||||
@@ -37,128 +34,109 @@ class DoubleEliminationGenerateLoserNames
|
||||
|
||||
mappings << {
|
||||
championship_bracket_position: champ_info[:bracket_position],
|
||||
consolation_bracket_position: conso_rounds[map_idx][:bracket_position],
|
||||
both_wrestlers: i.zero?,
|
||||
champ_round_index: i
|
||||
consolation_bracket_position: conso_rounds[map_idx][:bracket_position],
|
||||
both_wrestlers: i.zero?,
|
||||
champ_round_index: i
|
||||
}
|
||||
end
|
||||
|
||||
# Apply loser-name mappings
|
||||
mappings.each do |map|
|
||||
champ = matches.select { |m| m.bracket_position == map[:championship_bracket_position] }
|
||||
.sort_by(&:bracket_position_number)
|
||||
conso = matches.select { |m| m.bracket_position == map[:consolation_bracket_position] }
|
||||
.sort_by(&:bracket_position_number)
|
||||
|
||||
current_champ_round_index = map[:champ_round_index]
|
||||
if current_champ_round_index.odd?
|
||||
conso.reverse!
|
||||
end
|
||||
champ = rows.select { |r| r[:bracket_position] == map[:championship_bracket_position] }
|
||||
.sort_by { |r| r[:bracket_position_number] }
|
||||
conso = rows.select { |r| r[:bracket_position] == map[:consolation_bracket_position] }
|
||||
.sort_by { |r| r[:bracket_position_number] }
|
||||
conso.reverse! if map[:champ_round_index].odd?
|
||||
|
||||
idx = 0
|
||||
# Determine if this mapping is for losers from the first championship round
|
||||
is_first_champ_round_feed = map[:champ_round_index].zero?
|
||||
|
||||
is_first_feed = map[:champ_round_index].zero?
|
||||
conso.each do |cm|
|
||||
champ_match1 = champ[idx]
|
||||
if champ_match1
|
||||
if is_first_champ_round_feed && ((champ_match1.w1 && champ_match1.w2.nil?) || (champ_match1.w1.nil? && champ_match1.w2))
|
||||
cm.loser1_name = "BYE"
|
||||
if is_first_feed && single_competitor_match_row?(champ_match1)
|
||||
cm[:loser1_name] = "BYE"
|
||||
else
|
||||
cm.loser1_name = "Loser of #{champ_match1.bout_number}"
|
||||
cm[:loser1_name] = "Loser of #{champ_match1[:bout_number]}"
|
||||
end
|
||||
else
|
||||
cm.loser1_name = nil # Should not happen if bracket generation is correct
|
||||
cm[:loser1_name] = nil
|
||||
end
|
||||
|
||||
if map[:both_wrestlers] # This is true only if is_first_champ_round_feed
|
||||
idx += 1 # Increment for the second championship match
|
||||
if map[:both_wrestlers]
|
||||
idx += 1
|
||||
champ_match2 = champ[idx]
|
||||
if champ_match2
|
||||
# BYE check is only relevant for the first championship round feed
|
||||
if is_first_champ_round_feed && ((champ_match2.w1 && champ_match2.w2.nil?) || (champ_match2.w1.nil? && champ_match2.w2))
|
||||
cm.loser2_name = "BYE"
|
||||
if is_first_feed && single_competitor_match_row?(champ_match2)
|
||||
cm[:loser2_name] = "BYE"
|
||||
else
|
||||
cm.loser2_name = "Loser of #{champ_match2.bout_number}"
|
||||
cm[:loser2_name] = "Loser of #{champ_match2[:bout_number]}"
|
||||
end
|
||||
else
|
||||
cm.loser2_name = nil # Should not happen
|
||||
cm[:loser2_name] = nil
|
||||
end
|
||||
end
|
||||
idx += 1 # Increment for the next consolation match or next pair from championship
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
|
||||
# 5th/6th place
|
||||
if bracket_size >= 5 && num_placers >= 6 && weight.wrestlers.size > 4
|
||||
conso_semis = matches.select { |m| m.bracket_position == "Conso Semis" }
|
||||
.sort_by(&:bracket_position_number)
|
||||
if conso_semis.size >= 2
|
||||
m56 = matches.find { |m| m.bracket_position == "5/6" }
|
||||
m56.loser1_name = "Loser of #{conso_semis[0].bout_number}"
|
||||
m56.loser2_name = "Loser of #{conso_semis[1].bout_number}" if m56
|
||||
conso_semis = rows.select { |r| r[:bracket_position] == "Conso Semis" }.sort_by { |r| r[:bracket_position_number] }
|
||||
m56 = rows.find { |r| r[:bracket_position] == "5/6" }
|
||||
if conso_semis.size >= 2 && m56
|
||||
m56[:loser1_name] = "Loser of #{conso_semis[0][:bout_number]}"
|
||||
m56[:loser2_name] = "Loser of #{conso_semis[1][:bout_number]}"
|
||||
end
|
||||
end
|
||||
|
||||
# 7th/8th place
|
||||
if bracket_size >= 7 && num_placers >= 8 && weight.wrestlers.size > 6
|
||||
conso_quarters = matches.select { |m| m.bracket_position == "Conso Quarter" }
|
||||
.sort_by(&:bracket_position_number)
|
||||
if conso_quarters.size >= 2
|
||||
m78 = matches.find { |m| m.bracket_position == "7/8" }
|
||||
m78.loser1_name = "Loser of #{conso_quarters[0].bout_number}"
|
||||
m78.loser2_name = "Loser of #{conso_quarters[1].bout_number}" if m78
|
||||
conso_quarters = rows.select { |r| r[:bracket_position] == "Conso Quarter" }.sort_by { |r| r[:bracket_position_number] }
|
||||
m78 = rows.find { |r| r[:bracket_position] == "7/8" }
|
||||
if conso_quarters.size >= 2 && m78
|
||||
m78[:loser1_name] = "Loser of #{conso_quarters[0][:bout_number]}"
|
||||
m78[:loser2_name] = "Loser of #{conso_quarters[1][:bout_number]}"
|
||||
end
|
||||
end
|
||||
|
||||
matches.each(&:save!)
|
||||
end
|
||||
|
||||
# Advance first-round byes in championship bracket
|
||||
def advance_bye_matches_championship(weight)
|
||||
matches = weight.matches.reload
|
||||
first_round = matches.map(&:round).min
|
||||
matches.select { |m| m.round == first_round }
|
||||
.sort_by(&:bracket_position_number)
|
||||
.each { |m| handle_bye(m) }
|
||||
end
|
||||
|
||||
# Advance first-round byes in consolation bracket
|
||||
def advance_bye_matches_consolation(weight)
|
||||
matches = weight.matches.reload
|
||||
def assign_bye_outcomes_in_memory(weight, match_rows)
|
||||
bracket_size = weight.calculate_bracket_size
|
||||
first_conso = dynamic_consolation_rounds(bracket_size).first
|
||||
return if bracket_size <= 2
|
||||
|
||||
matches.select { |m| m.round == first_conso[:round] && m.bracket_position == first_conso[:bracket_position] }
|
||||
.sort_by(&:bracket_position_number)
|
||||
.each { |m| handle_bye(m) }
|
||||
end
|
||||
rows = match_rows.select { |r| r[:weight_id] == weight.id }
|
||||
first_round = rows.map { |r| r[:round] }.compact.min
|
||||
rows.select { |r| r[:round] == first_round }.each { |row| apply_bye_to_row(row) }
|
||||
|
||||
# Mark bye match, set finished, and advance
|
||||
def handle_bye(match)
|
||||
if [match.w1, match.w2].compact.size == 1
|
||||
match.finished = 1
|
||||
match.win_type = 'BYE'
|
||||
if match.w1
|
||||
match.winner_id = match.w1
|
||||
match.loser2_name = 'BYE'
|
||||
else
|
||||
match.winner_id = match.w2
|
||||
match.loser1_name = 'BYE'
|
||||
end
|
||||
match.score = ''
|
||||
match.save!
|
||||
match.advance_wrestlers
|
||||
first_conso = dynamic_consolation_rounds(bracket_size).first
|
||||
if first_conso
|
||||
rows.select { |r| r[:round] == first_conso[:round] && r[:bracket_position] == first_conso[:bracket_position] }
|
||||
.each { |row| apply_bye_to_row(row) }
|
||||
end
|
||||
end
|
||||
|
||||
# Helpers for dynamic bracket labels
|
||||
def apply_bye_to_row(row)
|
||||
return unless single_competitor_match_row?(row)
|
||||
|
||||
row[:finished] = 1
|
||||
row[:win_type] = "BYE"
|
||||
if row[:w1]
|
||||
row[:winner_id] = row[:w1]
|
||||
row[:loser2_name] = "BYE"
|
||||
else
|
||||
row[:winner_id] = row[:w2]
|
||||
row[:loser1_name] = "BYE"
|
||||
end
|
||||
row[:score] = ""
|
||||
end
|
||||
|
||||
def single_competitor_match_row?(row)
|
||||
[row[:w1], row[:w2]].compact.size == 1
|
||||
end
|
||||
|
||||
def first_round_label(size)
|
||||
case size
|
||||
when 2 then 'Final'
|
||||
when 4 then 'Semis'
|
||||
when 8 then 'Quarter'
|
||||
else "Bracket Round of #{size}"
|
||||
when 2 then "Final"
|
||||
when 4 then "Semis"
|
||||
when 8 then "Quarter"
|
||||
else "Bracket Round of #{size}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -186,10 +164,10 @@ class DoubleEliminationGenerateLoserNames
|
||||
|
||||
def bracket_label(participants)
|
||||
case participants
|
||||
when 2 then '1/2'
|
||||
when 4 then 'Semis'
|
||||
when 8 then 'Quarter'
|
||||
else "Bracket Round of #{participants}"
|
||||
when 2 then "1/2"
|
||||
when 4 then "Semis"
|
||||
when 8 then "Quarter"
|
||||
else "Bracket Round of #{participants}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,12 +175,12 @@ class DoubleEliminationGenerateLoserNames
|
||||
max_j_for_bracket = (2 * (Math.log2(bracket_size).to_i - 1) - 1)
|
||||
|
||||
if participants == 2 && j == max_j_for_bracket
|
||||
return '3/4'
|
||||
"3/4"
|
||||
elsif participants == 4
|
||||
return j.odd? ? 'Conso Quarter' : 'Conso Semis'
|
||||
j.odd? ? "Conso Quarter" : "Conso Semis"
|
||||
else
|
||||
suffix = j.odd? ? ".1" : ".2"
|
||||
return "Conso Round of #{participants}#{suffix}"
|
||||
"Conso Round of #{participants}#{suffix}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
class DoubleEliminationMatchGeneration
|
||||
def initialize(tournament)
|
||||
def initialize(tournament, weights: nil)
|
||||
@tournament = tournament
|
||||
@weights = weights
|
||||
end
|
||||
|
||||
def generate_matches
|
||||
#
|
||||
# PHASE 1: Generate matches (with local round definitions).
|
||||
#
|
||||
@tournament.weights.each do |weight|
|
||||
generate_matches_for_weight(weight)
|
||||
build_match_rows
|
||||
end
|
||||
|
||||
def build_match_rows
|
||||
rows_by_weight_id = {}
|
||||
|
||||
generation_weights.each do |weight|
|
||||
rows_by_weight_id[weight.id] = generate_match_rows_for_weight(weight)
|
||||
end
|
||||
|
||||
#
|
||||
# PHASE 2: Align all rounds to match the largest bracket’s definitions.
|
||||
#
|
||||
align_all_rounds_to_largest_bracket
|
||||
align_rows_to_largest_bracket(rows_by_weight_id)
|
||||
rows_by_weight_id.values.flatten
|
||||
end
|
||||
|
||||
###########################################################################
|
||||
# PHASE 1: Generate all matches for each bracket, using a single definition.
|
||||
###########################################################################
|
||||
def generate_matches_for_weight(weight)
|
||||
def generate_match_rows_for_weight(weight)
|
||||
bracket_size = weight.calculate_bracket_size
|
||||
bracket_info = define_bracket_matches(bracket_size)
|
||||
return unless bracket_info
|
||||
return [] unless bracket_info
|
||||
|
||||
rows = []
|
||||
|
||||
# 1) Round one matchups
|
||||
bracket_info[:round_one_matchups].each_with_index do |matchup, idx|
|
||||
@@ -32,7 +36,7 @@ class DoubleEliminationMatchGeneration
|
||||
bracket_pos_number = idx + 1
|
||||
round_number = matchup[:round]
|
||||
|
||||
create_matchup_from_seed(
|
||||
rows << create_matchup_from_seed(
|
||||
seed1,
|
||||
seed2,
|
||||
bracket_position,
|
||||
@@ -49,7 +53,7 @@ class DoubleEliminationMatchGeneration
|
||||
round_number = round_info[:round]
|
||||
|
||||
matches_this_round.times do |i|
|
||||
create_matchup(
|
||||
rows << create_matchup(
|
||||
nil,
|
||||
nil,
|
||||
bracket_position,
|
||||
@@ -67,7 +71,7 @@ class DoubleEliminationMatchGeneration
|
||||
round_number = round_info[:round]
|
||||
|
||||
matches_this_round.times do |i|
|
||||
create_matchup(
|
||||
rows << create_matchup(
|
||||
nil,
|
||||
nil,
|
||||
bracket_position,
|
||||
@@ -79,12 +83,14 @@ class DoubleEliminationMatchGeneration
|
||||
|
||||
# 5/6, 7/8 placing logic
|
||||
if weight.wrestlers.size >= 5 && @tournament.number_of_placers >= 6 && matches_this_round == 1
|
||||
create_matchup(nil, nil, "5/6", 1, round_number, weight)
|
||||
rows << create_matchup(nil, nil, "5/6", 1, round_number, weight)
|
||||
end
|
||||
if weight.wrestlers.size >= 7 && @tournament.number_of_placers >= 8 && matches_this_round == 1
|
||||
create_matchup(nil, nil, "7/8", 1, round_number, weight)
|
||||
rows << create_matchup(nil, nil, "7/8", 1, round_number, weight)
|
||||
end
|
||||
end
|
||||
|
||||
rows
|
||||
end
|
||||
|
||||
# Single bracket definition dynamically generated for any power-of-two bracket size.
|
||||
@@ -173,18 +179,18 @@ class DoubleEliminationMatchGeneration
|
||||
###########################################################################
|
||||
# PHASE 2: Overwrite rounds in all smaller brackets to match the largest one.
|
||||
###########################################################################
|
||||
def align_all_rounds_to_largest_bracket
|
||||
largest_weight = @tournament.weights.max_by { |w| w.calculate_bracket_size }
|
||||
def align_rows_to_largest_bracket(rows_by_weight_id)
|
||||
largest_weight = generation_weights.max_by { |w| w.calculate_bracket_size }
|
||||
return unless largest_weight
|
||||
|
||||
position_to_round = {}
|
||||
largest_weight.tournament.matches.where(weight_id: largest_weight.id).each do |m|
|
||||
position_to_round[m.bracket_position] ||= m.round
|
||||
rows_by_weight_id.fetch(largest_weight.id, []).each do |row|
|
||||
position_to_round[row[:bracket_position]] ||= row[:round]
|
||||
end
|
||||
|
||||
@tournament.matches.find_each do |match|
|
||||
if position_to_round.key?(match.bracket_position)
|
||||
match.update(round: position_to_round[match.bracket_position])
|
||||
rows_by_weight_id.each_value do |rows|
|
||||
rows.each do |row|
|
||||
row[:round] = position_to_round[row[:bracket_position]] if position_to_round.key?(row[:bracket_position])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -192,8 +198,12 @@ class DoubleEliminationMatchGeneration
|
||||
###########################################################################
|
||||
# Helper methods
|
||||
###########################################################################
|
||||
def generation_weights
|
||||
@weights || @tournament.weights.to_a
|
||||
end
|
||||
|
||||
def wrestler_with_seed(seed, weight)
|
||||
Wrestler.where("weight_id = ? AND bracket_line = ?", weight.id, seed).first&.id
|
||||
weight.wrestlers.find { |w| w.bracket_line == seed }&.id
|
||||
end
|
||||
|
||||
def create_matchup_from_seed(w1_seed, w2_seed, bracket_position, bracket_position_number, round, weight)
|
||||
@@ -208,14 +218,15 @@ class DoubleEliminationMatchGeneration
|
||||
end
|
||||
|
||||
def create_matchup(w1, w2, bracket_position, bracket_position_number, round, weight)
|
||||
weight.tournament.matches.create!(
|
||||
{
|
||||
w1: w1,
|
||||
w2: w2,
|
||||
tournament_id: weight.tournament_id,
|
||||
weight_id: weight.id,
|
||||
round: round,
|
||||
bracket_position: bracket_position,
|
||||
bracket_position_number: bracket_position_number
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
# Calculates the sequence of seeds for the first round of a power-of-two bracket.
|
||||
|
||||
@@ -10,62 +10,183 @@ class GenerateTournamentMatches
|
||||
|
||||
def generate_raw
|
||||
standardStartingActions
|
||||
PoolToBracketMatchGeneration.new(@tournament).generatePoolToBracketMatches if @tournament.tournament_type == "Pool to bracket"
|
||||
ModifiedSixteenManMatchGeneration.new(@tournament).generate_matches if @tournament.tournament_type.include? "Modified 16 Man Double Elimination"
|
||||
DoubleEliminationMatchGeneration.new(@tournament).generate_matches if @tournament.tournament_type.include? "Regular Double Elimination"
|
||||
generation_context = preload_generation_context
|
||||
seed_wrestlers_in_memory(generation_context)
|
||||
match_rows = build_match_rows(generation_context)
|
||||
post_process_match_rows_in_memory(generation_context, match_rows)
|
||||
persist_generation_rows(generation_context, match_rows)
|
||||
postMatchCreationActions
|
||||
PoolToBracketMatchGeneration.new(@tournament).assignLoserNames if @tournament.tournament_type == "Pool to bracket"
|
||||
ModifiedSixteenManGenerateLoserNames.new(@tournament).assign_loser_names if @tournament.tournament_type.include? "Modified 16 Man Double Elimination"
|
||||
DoubleEliminationGenerateLoserNames.new(@tournament).assign_loser_names if @tournament.tournament_type.include? "Regular Double Elimination"
|
||||
advance_bye_matches_after_insert
|
||||
end
|
||||
|
||||
def standardStartingActions
|
||||
@tournament.curently_generating_matches = 1
|
||||
@tournament.save
|
||||
WipeTournamentMatches.new(@tournament).setUpMatchGeneration
|
||||
TournamentSeeding.new(@tournament).set_seeds
|
||||
end
|
||||
|
||||
def preload_generation_context
|
||||
weights = @tournament.weights.includes(:wrestlers).order(:max).to_a
|
||||
wrestlers = weights.flat_map(&:wrestlers)
|
||||
{
|
||||
weights: weights,
|
||||
wrestlers: wrestlers,
|
||||
wrestlers_by_weight_id: wrestlers.group_by(&:weight_id)
|
||||
}
|
||||
end
|
||||
|
||||
def seed_wrestlers_in_memory(generation_context)
|
||||
TournamentSeeding.new(@tournament).set_seeds(weights: generation_context[:weights], persist: false)
|
||||
end
|
||||
|
||||
def build_match_rows(generation_context)
|
||||
return PoolToBracketMatchGeneration.new(
|
||||
@tournament,
|
||||
weights: generation_context[:weights],
|
||||
wrestlers_by_weight_id: generation_context[:wrestlers_by_weight_id]
|
||||
).generatePoolToBracketMatches if @tournament.tournament_type == "Pool to bracket"
|
||||
|
||||
return ModifiedSixteenManMatchGeneration.new(@tournament, weights: generation_context[:weights]).generate_matches if @tournament.tournament_type.include? "Modified 16 Man Double Elimination"
|
||||
|
||||
return DoubleEliminationMatchGeneration.new(@tournament, weights: generation_context[:weights]).generate_matches if @tournament.tournament_type.include? "Regular Double Elimination"
|
||||
|
||||
[]
|
||||
end
|
||||
|
||||
def persist_generation_rows(generation_context, match_rows)
|
||||
persist_wrestlers(generation_context[:wrestlers])
|
||||
persist_matches(match_rows)
|
||||
end
|
||||
|
||||
def post_process_match_rows_in_memory(generation_context, match_rows)
|
||||
move_finals_rows_to_last_round(match_rows) unless @tournament.tournament_type.include?("Regular Double Elimination")
|
||||
assign_bouts_in_memory(match_rows, generation_context[:weights])
|
||||
assign_loser_names_in_memory(generation_context, match_rows)
|
||||
assign_bye_outcomes_in_memory(generation_context, match_rows)
|
||||
end
|
||||
|
||||
def persist_wrestlers(wrestlers)
|
||||
return if wrestlers.blank?
|
||||
|
||||
timestamp = Time.current
|
||||
rows = wrestlers.map do |w|
|
||||
{
|
||||
id: w.id,
|
||||
bracket_line: w.bracket_line,
|
||||
pool: w.pool,
|
||||
updated_at: timestamp
|
||||
}
|
||||
end
|
||||
Wrestler.upsert_all(rows)
|
||||
end
|
||||
|
||||
def persist_matches(match_rows)
|
||||
return if match_rows.blank?
|
||||
|
||||
timestamp = Time.current
|
||||
rows_with_timestamps = match_rows.map do |row|
|
||||
row.to_h.symbolize_keys.merge(created_at: timestamp, updated_at: timestamp)
|
||||
end
|
||||
|
||||
all_keys = rows_with_timestamps.flat_map(&:keys).uniq
|
||||
normalized_rows = rows_with_timestamps.map do |row|
|
||||
all_keys.each_with_object({}) { |key, normalized| normalized[key] = row[key] }
|
||||
end
|
||||
|
||||
Match.insert_all!(normalized_rows)
|
||||
end
|
||||
|
||||
def postMatchCreationActions
|
||||
moveFinalsMatchesToLastRound if ! @tournament.tournament_type.include? "Regular Double Elimination"
|
||||
assignBouts
|
||||
@tournament.reset_and_fill_bout_board
|
||||
@tournament.curently_generating_matches = nil
|
||||
@tournament.save!
|
||||
Tournament.broadcast_up_matches_board(@tournament.id)
|
||||
end
|
||||
|
||||
def move_finals_rows_to_last_round(match_rows)
|
||||
finals_round = match_rows.map { |row| row[:round] }.compact.max
|
||||
return unless finals_round
|
||||
|
||||
match_rows.each do |row|
|
||||
row[:round] = finals_round if ["1/2", "3/4", "5/6", "7/8"].include?(row[:bracket_position])
|
||||
end
|
||||
end
|
||||
|
||||
def assign_bouts_in_memory(match_rows, weights)
|
||||
bout_counts = Hash.new(0)
|
||||
weight_max_by_id = weights.each_with_object({}) { |w, memo| memo[w.id] = w.max }
|
||||
|
||||
match_rows
|
||||
.sort_by { |row| [row[:round].to_i, weight_max_by_id[row[:weight_id]].to_f, row[:weight_id].to_i, row[:bracket_position_number].to_i] }
|
||||
.each do |row|
|
||||
round = row[:round].to_i
|
||||
row[:bout_number] = round * 1000 + bout_counts[round]
|
||||
bout_counts[round] += 1
|
||||
end
|
||||
end
|
||||
|
||||
def assign_loser_names_in_memory(generation_context, match_rows)
|
||||
if @tournament.tournament_type == "Pool to bracket"
|
||||
service = PoolToBracketGenerateLoserNames.new(@tournament)
|
||||
generation_context[:weights].each { |weight| service.assign_loser_names_in_memory(weight, match_rows) }
|
||||
elsif @tournament.tournament_type.include?("Modified 16 Man Double Elimination")
|
||||
service = ModifiedSixteenManGenerateLoserNames.new(@tournament)
|
||||
generation_context[:weights].each { |weight| service.assign_loser_names_in_memory(weight, match_rows) }
|
||||
elsif @tournament.tournament_type.include?("Regular Double Elimination")
|
||||
service = DoubleEliminationGenerateLoserNames.new(@tournament)
|
||||
generation_context[:weights].each { |weight| service.assign_loser_names_in_memory(weight, match_rows) }
|
||||
end
|
||||
end
|
||||
|
||||
def assign_bye_outcomes_in_memory(generation_context, match_rows)
|
||||
if @tournament.tournament_type.include?("Modified 16 Man Double Elimination")
|
||||
service = ModifiedSixteenManGenerateLoserNames.new(@tournament)
|
||||
generation_context[:weights].each { |weight| service.assign_bye_outcomes_in_memory(weight, match_rows) }
|
||||
elsif @tournament.tournament_type.include?("Regular Double Elimination")
|
||||
service = DoubleEliminationGenerateLoserNames.new(@tournament)
|
||||
generation_context[:weights].each { |weight| service.assign_bye_outcomes_in_memory(weight, match_rows) }
|
||||
end
|
||||
end
|
||||
|
||||
def advance_bye_matches_after_insert
|
||||
Match.where(tournament_id: @tournament.id, finished: 1, win_type: "BYE")
|
||||
.where.not(winner_id: nil)
|
||||
.find_each(&:advance_wrestlers)
|
||||
end
|
||||
|
||||
def assignBouts
|
||||
bout_counts = Hash.new(0)
|
||||
@tournament.matches.reload
|
||||
@tournament.matches.sort_by{|m| [m.round, m.weight_max]}.each do |m|
|
||||
m.bout_number = m.round * 1000 + bout_counts[m.round]
|
||||
bout_counts[m.round] += 1
|
||||
m.save!
|
||||
timestamp = Time.current
|
||||
ordered_matches = Match.joins(:weight)
|
||||
.where(tournament_id: @tournament.id)
|
||||
.order("matches.round ASC, weights.max ASC, matches.id ASC")
|
||||
.pluck("matches.id", "matches.round")
|
||||
|
||||
updates = []
|
||||
ordered_matches.each do |match_id, round|
|
||||
updates << {
|
||||
id: match_id,
|
||||
bout_number: round * 1000 + bout_counts[round],
|
||||
updated_at: timestamp
|
||||
}
|
||||
bout_counts[round] += 1
|
||||
end
|
||||
|
||||
Match.upsert_all(updates) if updates.any?
|
||||
end
|
||||
|
||||
def moveFinalsMatchesToLastRound
|
||||
finalsRound = @tournament.reload.total_rounds
|
||||
finalsMatches = @tournament.matches.reload.select{|m| m.bracket_position == "1/2" || m.bracket_position == "3/4" || m.bracket_position == "5/6" || m.bracket_position == "7/8"}
|
||||
finalsMatches. each do |m|
|
||||
m.round = finalsRound
|
||||
m.save
|
||||
end
|
||||
@tournament.matches
|
||||
.where(bracket_position: ["1/2", "3/4", "5/6", "7/8"])
|
||||
.update_all(round: finalsRound, updated_at: Time.current)
|
||||
end
|
||||
|
||||
def unAssignMats
|
||||
matches = @tournament.matches.reload
|
||||
matches.each do |m|
|
||||
m.mat_id = nil
|
||||
m.save!
|
||||
end
|
||||
@tournament.matches.update_all(mat_id: nil, updated_at: Time.current)
|
||||
end
|
||||
|
||||
def unAssignBouts
|
||||
bout_counts = Hash.new(0)
|
||||
@tournament.matches.each do |m|
|
||||
m.bout_number = nil
|
||||
m.save!
|
||||
end
|
||||
@tournament.matches.update_all(bout_number: nil, updated_at: Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,95 +1,91 @@
|
||||
class ModifiedSixteenManGenerateLoserNames
|
||||
def initialize( tournament )
|
||||
@tournament = tournament
|
||||
end
|
||||
|
||||
def assign_loser_names
|
||||
matches_by_weight = nil
|
||||
@tournament.weights.each do |w|
|
||||
matches_by_weight = @tournament.matches.where(weight_id: w.id)
|
||||
conso_round_2(matches_by_weight)
|
||||
conso_round_3(matches_by_weight)
|
||||
third_fourth(matches_by_weight)
|
||||
seventh_eighth(matches_by_weight)
|
||||
save_matches(matches_by_weight)
|
||||
matches_by_weight = @tournament.matches.where(weight_id: w.id).reload
|
||||
advance_bye_matches_championship(matches_by_weight)
|
||||
save_matches(matches_by_weight)
|
||||
end
|
||||
end
|
||||
|
||||
def conso_round_2(matches)
|
||||
matches.select{|m| m.bracket_position == "Conso Round of 8"}.sort_by{|m| m.bracket_position_number}.each do |match|
|
||||
if match.bracket_position_number == 1
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 1 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 2 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
elsif match.bracket_position_number == 2
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 3 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 4 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
elsif match.bracket_position_number == 3
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 5 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 6 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
elsif match.bracket_position_number == 4
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 7 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 8 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def conso_round_3(matches)
|
||||
matches.select{|m| m.bracket_position == "Conso Quarter"}.sort_by{|m| m.bracket_position_number}.each do |match|
|
||||
if match.bracket_position_number == 1
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 4 and m.bracket_position == "Quarter"}.first.bout_number}"
|
||||
elsif match.bracket_position_number == 2
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 3 and m.bracket_position == "Quarter"}.first.bout_number}"
|
||||
elsif match.bracket_position_number == 3
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 2 and m.bracket_position == "Quarter"}.first.bout_number}"
|
||||
elsif match.bracket_position_number == 4
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 1 and m.bracket_position == "Quarter"}.first.bout_number}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def third_fourth(matches)
|
||||
matches.select{|m| m.bracket_position == "3/4"}.sort_by{|m| m.bracket_position_number}.each do |match|
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position == "Semis"}.first.bout_number}"
|
||||
match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position == "Semis"}.second.bout_number}"
|
||||
end
|
||||
end
|
||||
|
||||
def seventh_eighth(matches)
|
||||
matches.select{|m| m.bracket_position == "7/8"}.sort_by{|m| m.bracket_position_number}.each do |match|
|
||||
match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position == "Conso Semis"}.first.bout_number}"
|
||||
match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position == "Conso Semis"}.second.bout_number}"
|
||||
end
|
||||
end
|
||||
|
||||
def advance_bye_matches_championship(matches)
|
||||
matches.select{|m| m.bracket_position == "Bracket Round of 16"}.sort_by{|m| m.bracket_position_number}.each do |match|
|
||||
if match.w1 == nil or match.w2 == nil
|
||||
match.finished = 1
|
||||
match.win_type = "BYE"
|
||||
if match.w1 != nil
|
||||
match.winner_id = match.w1
|
||||
match.loser2_name = "BYE"
|
||||
match.score = ""
|
||||
match.save
|
||||
match.advance_wrestlers
|
||||
elsif match.w2 != nil
|
||||
match.winner_id = match.w2
|
||||
match.loser1_name = "BYE"
|
||||
match.score = ""
|
||||
match.save
|
||||
match.advance_wrestlers
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def save_matches(matches)
|
||||
matches.each do |m|
|
||||
m.save!
|
||||
end
|
||||
def initialize(tournament)
|
||||
@tournament = tournament
|
||||
end
|
||||
|
||||
# Compatibility wrapper. Returns transformed rows and does not persist.
|
||||
def assign_loser_names(match_rows = nil)
|
||||
rows = match_rows || @tournament.matches.where(tournament_id: @tournament.id).map { |m| m.attributes.symbolize_keys }
|
||||
@tournament.weights.each do |weight|
|
||||
assign_loser_names_in_memory(weight, rows)
|
||||
assign_bye_outcomes_in_memory(weight, rows)
|
||||
end
|
||||
rows
|
||||
end
|
||||
|
||||
def assign_loser_names_in_memory(weight, match_rows)
|
||||
rows = match_rows.select { |row| row[:weight_id] == weight.id }
|
||||
round_16 = rows.select { |r| r[:bracket_position] == "Bracket Round of 16" }
|
||||
conso_8 = rows.select { |r| r[:bracket_position] == "Conso Round of 8" }.sort_by { |r| r[:bracket_position_number] }
|
||||
|
||||
conso_8.each do |row|
|
||||
if row[:bracket_position_number] == 1
|
||||
m1 = round_16.find { |m| m[:bracket_position_number] == 1 }
|
||||
m2 = round_16.find { |m| m[:bracket_position_number] == 2 }
|
||||
row[:loser1_name] = "Loser of #{m1[:bout_number]}" if m1
|
||||
row[:loser2_name] = "Loser of #{m2[:bout_number]}" if m2
|
||||
elsif row[:bracket_position_number] == 2
|
||||
m3 = round_16.find { |m| m[:bracket_position_number] == 3 }
|
||||
m4 = round_16.find { |m| m[:bracket_position_number] == 4 }
|
||||
row[:loser1_name] = "Loser of #{m3[:bout_number]}" if m3
|
||||
row[:loser2_name] = "Loser of #{m4[:bout_number]}" if m4
|
||||
elsif row[:bracket_position_number] == 3
|
||||
m5 = round_16.find { |m| m[:bracket_position_number] == 5 }
|
||||
m6 = round_16.find { |m| m[:bracket_position_number] == 6 }
|
||||
row[:loser1_name] = "Loser of #{m5[:bout_number]}" if m5
|
||||
row[:loser2_name] = "Loser of #{m6[:bout_number]}" if m6
|
||||
elsif row[:bracket_position_number] == 4
|
||||
m7 = round_16.find { |m| m[:bracket_position_number] == 7 }
|
||||
m8 = round_16.find { |m| m[:bracket_position_number] == 8 }
|
||||
row[:loser1_name] = "Loser of #{m7[:bout_number]}" if m7
|
||||
row[:loser2_name] = "Loser of #{m8[:bout_number]}" if m8
|
||||
end
|
||||
end
|
||||
|
||||
quarters = rows.select { |r| r[:bracket_position] == "Quarter" }
|
||||
conso_quarters = rows.select { |r| r[:bracket_position] == "Conso Quarter" }.sort_by { |r| r[:bracket_position_number] }
|
||||
conso_quarters.each do |row|
|
||||
source = case row[:bracket_position_number]
|
||||
when 1 then quarters.find { |q| q[:bracket_position_number] == 4 }
|
||||
when 2 then quarters.find { |q| q[:bracket_position_number] == 3 }
|
||||
when 3 then quarters.find { |q| q[:bracket_position_number] == 2 }
|
||||
when 4 then quarters.find { |q| q[:bracket_position_number] == 1 }
|
||||
end
|
||||
row[:loser1_name] = "Loser of #{source[:bout_number]}" if source
|
||||
end
|
||||
|
||||
semis = rows.select { |r| r[:bracket_position] == "Semis" }
|
||||
third_fourth = rows.find { |r| r[:bracket_position] == "3/4" }
|
||||
if third_fourth
|
||||
third_fourth[:loser1_name] = "Loser of #{semis.first[:bout_number]}" if semis.first
|
||||
third_fourth[:loser2_name] = "Loser of #{semis.second[:bout_number]}" if semis.second
|
||||
end
|
||||
|
||||
conso_semis = rows.select { |r| r[:bracket_position] == "Conso Semis" }
|
||||
seventh_eighth = rows.find { |r| r[:bracket_position] == "7/8" }
|
||||
if seventh_eighth
|
||||
seventh_eighth[:loser1_name] = "Loser of #{conso_semis.first[:bout_number]}" if conso_semis.first
|
||||
seventh_eighth[:loser2_name] = "Loser of #{conso_semis.second[:bout_number]}" if conso_semis.second
|
||||
end
|
||||
end
|
||||
|
||||
def assign_bye_outcomes_in_memory(weight, match_rows)
|
||||
rows = match_rows.select { |r| r[:weight_id] == weight.id && r[:bracket_position] == "Bracket Round of 16" }
|
||||
rows.each { |row| apply_bye_to_row(row) }
|
||||
end
|
||||
|
||||
def apply_bye_to_row(row)
|
||||
return unless [row[:w1], row[:w2]].compact.size == 1
|
||||
|
||||
row[:finished] = 1
|
||||
row[:win_type] = "BYE"
|
||||
if row[:w1]
|
||||
row[:winner_id] = row[:w1]
|
||||
row[:loser2_name] = "BYE"
|
||||
else
|
||||
row[:winner_id] = row[:w2]
|
||||
row[:loser1_name] = "BYE"
|
||||
end
|
||||
row[:score] = ""
|
||||
end
|
||||
end
|
||||
@@ -1,70 +1,75 @@
|
||||
class ModifiedSixteenManMatchGeneration
|
||||
def initialize( tournament )
|
||||
def initialize( tournament, weights: nil )
|
||||
@tournament = tournament
|
||||
@number_of_placers = @tournament.number_of_placers
|
||||
@weights = weights
|
||||
end
|
||||
|
||||
def generate_matches
|
||||
@tournament.weights.each do |weight|
|
||||
generate_matches_for_weight(weight)
|
||||
rows = []
|
||||
generation_weights.each do |weight|
|
||||
rows.concat(generate_match_rows_for_weight(weight))
|
||||
end
|
||||
rows
|
||||
end
|
||||
|
||||
def generate_matches_for_weight(weight)
|
||||
round_one(weight)
|
||||
round_two(weight)
|
||||
round_three(weight)
|
||||
round_four(weight)
|
||||
round_five(weight)
|
||||
def generate_match_rows_for_weight(weight)
|
||||
rows = []
|
||||
round_one(weight, rows)
|
||||
round_two(weight, rows)
|
||||
round_three(weight, rows)
|
||||
round_four(weight, rows)
|
||||
round_five(weight, rows)
|
||||
rows
|
||||
end
|
||||
|
||||
def round_one(weight)
|
||||
create_matchup_from_seed(1,16, "Bracket Round of 16", 1, 1,weight)
|
||||
create_matchup_from_seed(8,9, "Bracket Round of 16", 2, 1,weight)
|
||||
create_matchup_from_seed(5,12, "Bracket Round of 16", 3, 1,weight)
|
||||
create_matchup_from_seed(4,14, "Bracket Round of 16", 4, 1,weight)
|
||||
create_matchup_from_seed(3,13, "Bracket Round of 16", 5, 1,weight)
|
||||
create_matchup_from_seed(6,11, "Bracket Round of 16", 6, 1,weight)
|
||||
create_matchup_from_seed(7,10, "Bracket Round of 16", 7, 1,weight)
|
||||
create_matchup_from_seed(2,15, "Bracket Round of 16", 8, 1,weight)
|
||||
def round_one(weight, rows)
|
||||
rows << create_matchup_from_seed(1,16, "Bracket Round of 16", 1, 1,weight)
|
||||
rows << create_matchup_from_seed(8,9, "Bracket Round of 16", 2, 1,weight)
|
||||
rows << create_matchup_from_seed(5,12, "Bracket Round of 16", 3, 1,weight)
|
||||
rows << create_matchup_from_seed(4,14, "Bracket Round of 16", 4, 1,weight)
|
||||
rows << create_matchup_from_seed(3,13, "Bracket Round of 16", 5, 1,weight)
|
||||
rows << create_matchup_from_seed(6,11, "Bracket Round of 16", 6, 1,weight)
|
||||
rows << create_matchup_from_seed(7,10, "Bracket Round of 16", 7, 1,weight)
|
||||
rows << create_matchup_from_seed(2,15, "Bracket Round of 16", 8, 1,weight)
|
||||
end
|
||||
|
||||
def round_two(weight)
|
||||
create_matchup(nil,nil,"Quarter",1,2,weight)
|
||||
create_matchup(nil,nil,"Quarter",2,2,weight)
|
||||
create_matchup(nil,nil,"Quarter",3,2,weight)
|
||||
create_matchup(nil,nil,"Quarter",4,2,weight)
|
||||
create_matchup(nil,nil,"Conso Round of 8",1,2,weight)
|
||||
create_matchup(nil,nil,"Conso Round of 8",2,2,weight)
|
||||
create_matchup(nil,nil,"Conso Round of 8",3,2,weight)
|
||||
create_matchup(nil,nil,"Conso Round of 8",4,2,weight)
|
||||
def round_two(weight, rows)
|
||||
rows << create_matchup(nil,nil,"Quarter",1,2,weight)
|
||||
rows << create_matchup(nil,nil,"Quarter",2,2,weight)
|
||||
rows << create_matchup(nil,nil,"Quarter",3,2,weight)
|
||||
rows << create_matchup(nil,nil,"Quarter",4,2,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Round of 8",1,2,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Round of 8",2,2,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Round of 8",3,2,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Round of 8",4,2,weight)
|
||||
end
|
||||
|
||||
def round_three(weight)
|
||||
create_matchup(nil,nil,"Semis",1,3,weight)
|
||||
create_matchup(nil,nil,"Semis",2,3,weight)
|
||||
create_matchup(nil,nil,"Conso Quarter",1,3,weight)
|
||||
create_matchup(nil,nil,"Conso Quarter",2,3,weight)
|
||||
create_matchup(nil,nil,"Conso Quarter",3,3,weight)
|
||||
create_matchup(nil,nil,"Conso Quarter",4,3,weight)
|
||||
def round_three(weight, rows)
|
||||
rows << create_matchup(nil,nil,"Semis",1,3,weight)
|
||||
rows << create_matchup(nil,nil,"Semis",2,3,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Quarter",1,3,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Quarter",2,3,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Quarter",3,3,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Quarter",4,3,weight)
|
||||
end
|
||||
|
||||
def round_four(weight)
|
||||
create_matchup(nil,nil,"Conso Semis",1,4,weight)
|
||||
create_matchup(nil,nil,"Conso Semis",2,4,weight)
|
||||
def round_four(weight, rows)
|
||||
rows << create_matchup(nil,nil,"Conso Semis",1,4,weight)
|
||||
rows << create_matchup(nil,nil,"Conso Semis",2,4,weight)
|
||||
end
|
||||
|
||||
def round_five(weight)
|
||||
create_matchup(nil,nil,"1/2",1,5,weight)
|
||||
create_matchup(nil,nil,"3/4",1,5,weight)
|
||||
create_matchup(nil,nil,"5/6",1,5,weight)
|
||||
def round_five(weight, rows)
|
||||
rows << create_matchup(nil,nil,"1/2",1,5,weight)
|
||||
rows << create_matchup(nil,nil,"3/4",1,5,weight)
|
||||
rows << create_matchup(nil,nil,"5/6",1,5,weight)
|
||||
if @number_of_placers >= 8
|
||||
create_matchup(nil,nil,"7/8",1,5,weight)
|
||||
rows << create_matchup(nil,nil,"7/8",1,5,weight)
|
||||
end
|
||||
end
|
||||
|
||||
def wrestler_with_seed(seed,weight)
|
||||
wrestler = Wrestler.where("weight_id = ? and bracket_line = ?", weight.id, seed).first
|
||||
wrestler = weight.wrestlers.find { |w| w.bracket_line == seed }
|
||||
if wrestler
|
||||
return wrestler.id
|
||||
else
|
||||
@@ -79,13 +84,18 @@ class ModifiedSixteenManMatchGeneration
|
||||
end
|
||||
|
||||
def create_matchup(w1, w2, bracket_position, bracket_position_number,round,weight)
|
||||
@tournament.matches.create(
|
||||
{
|
||||
w1: w1,
|
||||
w2: w2,
|
||||
tournament_id: @tournament.id,
|
||||
weight_id: weight.id,
|
||||
round: round,
|
||||
bracket_position: bracket_position,
|
||||
bracket_position_number: bracket_position_number
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def generation_weights
|
||||
@weights || @tournament.weights.to_a
|
||||
end
|
||||
end
|
||||
@@ -12,18 +12,19 @@ class PoolBracketGeneration
|
||||
end
|
||||
|
||||
def generateBracketMatches()
|
||||
@rows = []
|
||||
if @pool_bracket_type == "twoPoolsToSemi"
|
||||
return twoPoolsToSemi()
|
||||
twoPoolsToSemi()
|
||||
elsif @pool_bracket_type == "twoPoolsToFinal"
|
||||
return twoPoolsToFinal()
|
||||
twoPoolsToFinal()
|
||||
elsif @pool_bracket_type == "fourPoolsToQuarter"
|
||||
return fourPoolsToQuarter()
|
||||
fourPoolsToQuarter()
|
||||
elsif @pool_bracket_type == "fourPoolsToSemi"
|
||||
return fourPoolsToSemi()
|
||||
fourPoolsToSemi()
|
||||
elsif @pool_bracket_type == "eightPoolsToQuarter"
|
||||
return eightPoolsToQuarter()
|
||||
eightPoolsToQuarter()
|
||||
end
|
||||
return []
|
||||
@rows
|
||||
end
|
||||
|
||||
def twoPoolsToSemi()
|
||||
@@ -86,14 +87,15 @@ class PoolBracketGeneration
|
||||
end
|
||||
|
||||
def createMatchup(w1_name, w2_name, bracket_position, bracket_position_number)
|
||||
@tournament.matches.create(
|
||||
@rows << {
|
||||
loser1_name: w1_name,
|
||||
loser2_name: w2_name,
|
||||
tournament_id: @tournament.id,
|
||||
weight_id: @weight.id,
|
||||
round: @round,
|
||||
bracket_position: bracket_position,
|
||||
bracket_position_number: bracket_position_number
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,35 +1,46 @@
|
||||
class PoolGeneration
|
||||
def initialize(weight)
|
||||
def initialize(weight, wrestlers: nil)
|
||||
@weight = weight
|
||||
@tournament = @weight.tournament
|
||||
@pool = 1
|
||||
@wrestlers = wrestlers
|
||||
end
|
||||
|
||||
def generatePools
|
||||
GeneratePoolNumbers.new(@weight).savePoolNumbers
|
||||
GeneratePoolNumbers.new(@weight).savePoolNumbers(wrestlers: wrestlers_for_weight, persist: false)
|
||||
rows = []
|
||||
pools = @weight.pools
|
||||
while @pool <= pools
|
||||
roundRobin
|
||||
rows.concat(roundRobin)
|
||||
@pool += 1
|
||||
end
|
||||
rows
|
||||
end
|
||||
|
||||
def roundRobin
|
||||
wrestlers = @weight.wrestlers_in_pool(@pool)
|
||||
rows = []
|
||||
wrestlers = wrestlers_for_weight.select { |w| w.pool == @pool }
|
||||
pool_matches = RoundRobinTournament.schedule(wrestlers).reverse
|
||||
pool_matches.each_with_index do |b, index|
|
||||
round = index + 1
|
||||
bouts = b.map
|
||||
bouts.each do |bout|
|
||||
if bout[0] != nil and bout[1] != nil
|
||||
@tournament.matches.create(
|
||||
rows << {
|
||||
w1: bout[0].id,
|
||||
w2: bout[1].id,
|
||||
tournament_id: @tournament.id,
|
||||
weight_id: @weight.id,
|
||||
bracket_position: "Pool",
|
||||
round: round)
|
||||
round: round
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
rows
|
||||
end
|
||||
|
||||
def wrestlers_for_weight
|
||||
@wrestlers || @weight.wrestlers.to_a
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,80 +1,97 @@
|
||||
class PoolToBracketGenerateLoserNames
|
||||
def initialize( tournament )
|
||||
@tournament = tournament
|
||||
def initialize(tournament)
|
||||
@tournament = tournament
|
||||
end
|
||||
|
||||
# Compatibility wrapper. Returns transformed rows and does not persist.
|
||||
def assignLoserNamesWeight(weight, match_rows = nil)
|
||||
rows = match_rows || @tournament.matches.where(weight_id: weight.id).map { |m| m.attributes.symbolize_keys }
|
||||
assign_loser_names_in_memory(weight, rows)
|
||||
rows
|
||||
end
|
||||
|
||||
# Compatibility wrapper. Returns transformed rows and does not persist.
|
||||
def assignLoserNames
|
||||
@tournament.weights.each_with_object([]) do |weight, all_rows|
|
||||
all_rows.concat(assignLoserNamesWeight(weight))
|
||||
end
|
||||
end
|
||||
|
||||
def assign_loser_names_in_memory(weight, match_rows)
|
||||
rows = match_rows.select { |row| row[:weight_id] == weight.id }
|
||||
if weight.pool_bracket_type == "twoPoolsToSemi"
|
||||
two_pools_to_semi_loser_rows(rows)
|
||||
elsif (weight.pool_bracket_type == "fourPoolsToQuarter") || (weight.pool_bracket_type == "eightPoolsToQuarter")
|
||||
four_pools_to_quarter_loser_rows(rows)
|
||||
elsif weight.pool_bracket_type == "fourPoolsToSemi"
|
||||
four_pools_to_semi_loser_rows(rows)
|
||||
end
|
||||
end
|
||||
|
||||
def two_pools_to_semi_loser_rows(rows)
|
||||
match1 = rows.find { |m| m[:loser1_name] == "Winner Pool 1" }
|
||||
match2 = rows.find { |m| m[:loser1_name] == "Winner Pool 2" }
|
||||
match_change = rows.find { |m| m[:bracket_position] == "3/4" }
|
||||
return unless match1 && match2 && match_change
|
||||
|
||||
match_change[:loser1_name] = "Loser of #{match1[:bout_number]}"
|
||||
match_change[:loser2_name] = "Loser of #{match2[:bout_number]}"
|
||||
end
|
||||
|
||||
def four_pools_to_quarter_loser_rows(rows)
|
||||
quarters = rows.select { |m| m[:bracket_position] == "Quarter" }
|
||||
conso_semis = rows.select { |m| m[:bracket_position] == "Conso Semis" }
|
||||
semis = rows.select { |m| m[:bracket_position] == "Semis" }
|
||||
third_fourth = rows.find { |m| m[:bracket_position] == "3/4" }
|
||||
seventh_eighth = rows.find { |m| m[:bracket_position] == "7/8" }
|
||||
|
||||
conso_semis.each do |m|
|
||||
if m[:bracket_position_number] == 1
|
||||
q1 = quarters.find { |q| q[:bracket_position_number] == 1 }
|
||||
q2 = quarters.find { |q| q[:bracket_position_number] == 2 }
|
||||
m[:loser1_name] = "Loser of #{q1[:bout_number]}" if q1
|
||||
m[:loser2_name] = "Loser of #{q2[:bout_number]}" if q2
|
||||
elsif m[:bracket_position_number] == 2
|
||||
q3 = quarters.find { |q| q[:bracket_position_number] == 3 }
|
||||
q4 = quarters.find { |q| q[:bracket_position_number] == 4 }
|
||||
m[:loser1_name] = "Loser of #{q3[:bout_number]}" if q3
|
||||
m[:loser2_name] = "Loser of #{q4[:bout_number]}" if q4
|
||||
end
|
||||
end
|
||||
|
||||
def assignLoserNamesWeight(weight)
|
||||
matches_by_weight = @tournament.matches.where(weight_id: weight.id)
|
||||
if weight.pool_bracket_type == "twoPoolsToSemi"
|
||||
twoPoolsToSemiLoser(matches_by_weight)
|
||||
elsif (weight.pool_bracket_type == "fourPoolsToQuarter") or (weight.pool_bracket_type == "eightPoolsToQuarter")
|
||||
fourPoolsToQuarterLoser(matches_by_weight)
|
||||
elsif weight.pool_bracket_type == "fourPoolsToSemi"
|
||||
fourPoolsToSemiLoser(matches_by_weight)
|
||||
end
|
||||
saveMatches(matches_by_weight)
|
||||
end
|
||||
|
||||
def assignLoserNames
|
||||
matches_by_weight = nil
|
||||
@tournament.weights.each do |w|
|
||||
matches_by_weight = @tournament.matches.where(weight_id: w.id)
|
||||
if w.pool_bracket_type == "twoPoolsToSemi"
|
||||
twoPoolsToSemiLoser(matches_by_weight)
|
||||
elsif (w.pool_bracket_type == "fourPoolsToQuarter") or (w.pool_bracket_type == "eightPoolsToQuarter")
|
||||
fourPoolsToQuarterLoser(matches_by_weight)
|
||||
elsif w.pool_bracket_type == "fourPoolsToSemi"
|
||||
fourPoolsToSemiLoser(matches_by_weight)
|
||||
end
|
||||
saveMatches(matches_by_weight)
|
||||
if third_fourth
|
||||
s1 = semis.find { |s| s[:bracket_position_number] == 1 }
|
||||
s2 = semis.find { |s| s[:bracket_position_number] == 2 }
|
||||
third_fourth[:loser1_name] = "Loser of #{s1[:bout_number]}" if s1
|
||||
third_fourth[:loser2_name] = "Loser of #{s2[:bout_number]}" if s2
|
||||
end
|
||||
end
|
||||
|
||||
def twoPoolsToSemiLoser(matches_by_weight)
|
||||
match1 = matches_by_weight.select{|m| m.loser1_name == "Winner Pool 1"}.first
|
||||
match2 = matches_by_weight.select{|m| m.loser1_name == "Winner Pool 2"}.first
|
||||
matchChange = matches_by_weight.select{|m| m.bracket_position == "3/4"}.first
|
||||
matchChange.loser1_name = "Loser of #{match1.bout_number}"
|
||||
matchChange.loser2_name = "Loser of #{match2.bout_number}"
|
||||
end
|
||||
|
||||
def fourPoolsToQuarterLoser(matches_by_weight)
|
||||
quarters = matches_by_weight.select{|m| m.bracket_position == "Quarter"}
|
||||
consoSemis = matches_by_weight.select{|m| m.bracket_position == "Conso Semis"}
|
||||
semis = matches_by_weight.select{|m| m.bracket_position == "Semis"}
|
||||
thirdFourth = matches_by_weight.select{|m| m.bracket_position == "3/4"}.first
|
||||
seventhEighth = matches_by_weight.select{|m| m.bracket_position == "7/8"}.first
|
||||
consoSemis.each do |m|
|
||||
if m.bracket_position_number == 1
|
||||
m.loser1_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 1}.first.bout_number}"
|
||||
m.loser2_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 2}.first.bout_number}"
|
||||
elsif m.bracket_position_number == 2
|
||||
m.loser1_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 3}.first.bout_number}"
|
||||
m.loser2_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 4}.first.bout_number}"
|
||||
end
|
||||
if seventh_eighth
|
||||
c1 = conso_semis.find { |c| c[:bracket_position_number] == 1 }
|
||||
c2 = conso_semis.find { |c| c[:bracket_position_number] == 2 }
|
||||
seventh_eighth[:loser1_name] = "Loser of #{c1[:bout_number]}" if c1
|
||||
seventh_eighth[:loser2_name] = "Loser of #{c2[:bout_number]}" if c2
|
||||
end
|
||||
thirdFourth.loser1_name = "Loser of #{semis.select{|m| m.bracket_position_number == 1}.first.bout_number}"
|
||||
thirdFourth.loser2_name = "Loser of #{semis.select{|m| m.bracket_position_number == 2}.first.bout_number}"
|
||||
consoSemis = matches_by_weight.select{|m| m.bracket_position == "Conso Semis"}
|
||||
seventhEighth.loser1_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 1}.first.bout_number}"
|
||||
seventhEighth.loser2_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 2}.first.bout_number}"
|
||||
end
|
||||
|
||||
def fourPoolsToSemiLoser(matches_by_weight)
|
||||
semis = matches_by_weight.select{|m| m.bracket_position == "Semis"}
|
||||
thirdFourth = matches_by_weight.select{|m| m.bracket_position == "3/4"}.first
|
||||
consoSemis = matches_by_weight.select{|m| m.bracket_position == "Conso Semis"}
|
||||
seventhEighth = matches_by_weight.select{|m| m.bracket_position == "7/8"}.first
|
||||
thirdFourth.loser1_name = "Loser of #{semis.select{|m| m.bracket_position_number == 1}.first.bout_number}"
|
||||
thirdFourth.loser2_name = "Loser of #{semis.select{|m| m.bracket_position_number == 2}.first.bout_number}"
|
||||
seventhEighth.loser1_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 1}.first.bout_number}"
|
||||
seventhEighth.loser2_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 2}.first.bout_number}"
|
||||
end
|
||||
def four_pools_to_semi_loser_rows(rows)
|
||||
semis = rows.select { |m| m[:bracket_position] == "Semis" }
|
||||
conso_semis = rows.select { |m| m[:bracket_position] == "Conso Semis" }
|
||||
third_fourth = rows.find { |m| m[:bracket_position] == "3/4" }
|
||||
seventh_eighth = rows.find { |m| m[:bracket_position] == "7/8" }
|
||||
|
||||
def saveMatches(matches)
|
||||
matches.each do |m|
|
||||
m.save!
|
||||
end
|
||||
end
|
||||
if third_fourth
|
||||
s1 = semis.find { |s| s[:bracket_position_number] == 1 }
|
||||
s2 = semis.find { |s| s[:bracket_position_number] == 2 }
|
||||
third_fourth[:loser1_name] = "Loser of #{s1[:bout_number]}" if s1
|
||||
third_fourth[:loser2_name] = "Loser of #{s2[:bout_number]}" if s2
|
||||
end
|
||||
|
||||
if seventh_eighth
|
||||
c1 = conso_semis.find { |c| c[:bracket_position_number] == 1 }
|
||||
c2 = conso_semis.find { |c| c[:bracket_position_number] == 2 }
|
||||
seventh_eighth[:loser1_name] = "Loser of #{c1[:bout_number]}" if c1
|
||||
seventh_eighth[:loser2_name] = "Loser of #{c2[:bout_number]}" if c2
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,41 +1,89 @@
|
||||
class PoolToBracketMatchGeneration
|
||||
def initialize( tournament )
|
||||
def initialize(tournament, weights: nil, wrestlers_by_weight_id: nil)
|
||||
@tournament = tournament
|
||||
@weights = weights
|
||||
@wrestlers_by_weight_id = wrestlers_by_weight_id
|
||||
end
|
||||
|
||||
def generatePoolToBracketMatches
|
||||
@tournament.weights.order(:max).each do |weight|
|
||||
PoolGeneration.new(weight).generatePools()
|
||||
last_match = @tournament.matches.where(weight: weight).order(round: :desc).limit(1).first
|
||||
highest_round = last_match.round
|
||||
PoolBracketGeneration.new(weight, highest_round).generateBracketMatches()
|
||||
rows = []
|
||||
generation_weights.each do |weight|
|
||||
wrestlers = wrestlers_for_weight(weight)
|
||||
pool_rows = PoolGeneration.new(weight, wrestlers: wrestlers).generatePools
|
||||
rows.concat(pool_rows)
|
||||
|
||||
highest_round = pool_rows.map { |row| row[:round] }.max || 0
|
||||
bracket_rows = PoolBracketGeneration.new(weight, highest_round).generateBracketMatches
|
||||
rows.concat(bracket_rows)
|
||||
end
|
||||
movePoolSeedsToFinalPoolRound
|
||||
|
||||
movePoolSeedsToFinalPoolRound(rows)
|
||||
rows
|
||||
end
|
||||
|
||||
def movePoolSeedsToFinalPoolRound
|
||||
@tournament.weights.each do |w|
|
||||
setOriginalSeedsToWrestleLastPoolRound(w)
|
||||
def movePoolSeedsToFinalPoolRound(match_rows)
|
||||
generation_weights.each do |w|
|
||||
setOriginalSeedsToWrestleLastPoolRound(w, match_rows)
|
||||
end
|
||||
end
|
||||
|
||||
def setOriginalSeedsToWrestleLastPoolRound(weight)
|
||||
def setOriginalSeedsToWrestleLastPoolRound(weight, match_rows)
|
||||
pool = 1
|
||||
until pool > weight.pools
|
||||
wrestler1 = weight.pool_wrestlers_sorted_by_bracket_line(pool).first
|
||||
wrestler2 = weight.pool_wrestlers_sorted_by_bracket_line(pool).second
|
||||
match = wrestler1.pool_matches.sort_by{|m| m.round}.last
|
||||
if match.w1 != wrestler2.id or match.w2 != wrestler2.id
|
||||
if match.w1 == wrestler1.id
|
||||
SwapWrestlers.new.swap_wrestlers_bracket_lines(match.w2,wrestler2.id)
|
||||
elsif match.w2 == wrestler1.id
|
||||
SwapWrestlers.new.swap_wrestlers_bracket_lines(match.w1,wrestler2.id)
|
||||
end
|
||||
wrestlers = wrestlers_for_weight(weight)
|
||||
weight_pools = weight.pools
|
||||
until pool > weight_pools
|
||||
pool_wrestlers = wrestlers.select { |w| w.pool == pool }.sort_by(&:bracket_line)
|
||||
wrestler1 = pool_wrestlers.first
|
||||
wrestler2 = pool_wrestlers.second
|
||||
if wrestler1 && wrestler2
|
||||
pool_matches = match_rows.select { |row| row[:weight_id] == weight.id && row[:bracket_position] == "Pool" && (row[:w1] == wrestler1.id || row[:w2] == wrestler1.id) }
|
||||
match = pool_matches.max_by { |row| row[:round] }
|
||||
if match && (match[:w1] != wrestler2.id || match[:w2] != wrestler2.id)
|
||||
if match[:w1] == wrestler1.id
|
||||
swap_wrestlers_in_memory(match_rows, wrestlers, match[:w2], wrestler2.id)
|
||||
elsif match[:w2] == wrestler1.id
|
||||
swap_wrestlers_in_memory(match_rows, wrestlers, match[:w1], wrestler2.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
pool += 1
|
||||
end
|
||||
end
|
||||
|
||||
def swap_wrestlers_in_memory(match_rows, wrestlers, wrestler1_id, wrestler2_id)
|
||||
w1 = wrestlers.find { |w| w.id == wrestler1_id }
|
||||
w2 = wrestlers.find { |w| w.id == wrestler2_id }
|
||||
return unless w1 && w2
|
||||
|
||||
w1_bracket_line, w1_pool = w1.bracket_line, w1.pool
|
||||
w1.bracket_line, w1.pool = w2.bracket_line, w2.pool
|
||||
w2.bracket_line, w2.pool = w1_bracket_line, w1_pool
|
||||
|
||||
swap_match_rows(match_rows, wrestler1_id, wrestler2_id)
|
||||
end
|
||||
|
||||
def swap_match_rows(match_rows, wrestler1_id, wrestler2_id)
|
||||
match_rows.each do |row|
|
||||
row[:w1] = swap_id(row[:w1], wrestler1_id, wrestler2_id)
|
||||
row[:w2] = swap_id(row[:w2], wrestler1_id, wrestler2_id)
|
||||
row[:winner_id] = swap_id(row[:winner_id], wrestler1_id, wrestler2_id)
|
||||
end
|
||||
end
|
||||
|
||||
def swap_id(value, wrestler1_id, wrestler2_id)
|
||||
return wrestler2_id if value == wrestler1_id
|
||||
return wrestler1_id if value == wrestler2_id
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
def generation_weights
|
||||
@weights || @tournament.weights.order(:max).to_a
|
||||
end
|
||||
|
||||
def wrestlers_for_weight(weight)
|
||||
@wrestlers_by_weight_id&.fetch(weight.id, nil) || weight.wrestlers.to_a
|
||||
end
|
||||
|
||||
def assignLoserNames
|
||||
PoolToBracketGenerateLoserNames.new(@tournament).assignLoserNames
|
||||
|
||||
@@ -3,16 +3,22 @@ class TournamentSeeding
|
||||
@tournament = tournament
|
||||
end
|
||||
|
||||
def set_seeds
|
||||
@tournament.weights.each do |weight|
|
||||
def set_seeds(weights: nil, persist: true)
|
||||
weights_to_seed = weights || @tournament.weights.includes(:wrestlers)
|
||||
updated_wrestlers = []
|
||||
|
||||
weights_to_seed.each do |weight|
|
||||
wrestlers = weight.wrestlers
|
||||
bracket_size = weight.calculate_bracket_size
|
||||
|
||||
wrestlers = reset_bracket_line_for_lines_higher_than_bracket_size(wrestlers, bracket_size)
|
||||
wrestlers = set_original_seed_to_bracket_line(wrestlers)
|
||||
wrestlers = random_seeding(wrestlers, bracket_size)
|
||||
wrestlers.each(&:save)
|
||||
updated_wrestlers.concat(wrestlers)
|
||||
end
|
||||
|
||||
persist_bracket_lines(updated_wrestlers) if persist
|
||||
updated_wrestlers
|
||||
end
|
||||
|
||||
def random_seeding(wrestlers, bracket_size)
|
||||
@@ -96,4 +102,19 @@ class TournamentSeeding
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def persist_bracket_lines(wrestlers)
|
||||
return if wrestlers.blank?
|
||||
|
||||
timestamp = Time.current
|
||||
updates = wrestlers.map do |wrestler|
|
||||
{
|
||||
id: wrestler.id,
|
||||
bracket_line: wrestler.bracket_line,
|
||||
updated_at: timestamp
|
||||
}
|
||||
end
|
||||
|
||||
Wrestler.upsert_all(updates)
|
||||
end
|
||||
end
|
||||
@@ -14,7 +14,7 @@ class WipeTournamentMatches
|
||||
end
|
||||
|
||||
def wipeMatches
|
||||
@tournament.matches.destroy_all
|
||||
@tournament.destroy_all_matches
|
||||
end
|
||||
|
||||
def resetSchoolScores
|
||||
|
||||
@@ -3,11 +3,13 @@ class GeneratePoolNumbers
|
||||
@weight = weight
|
||||
end
|
||||
|
||||
def savePoolNumbers
|
||||
@weight.wrestlers.each do |wrestler|
|
||||
def savePoolNumbers(wrestlers: nil, persist: true)
|
||||
wrestlers_to_update = wrestlers || @weight.wrestlers.to_a
|
||||
wrestlers_to_update.each do |wrestler|
|
||||
wrestler.pool = get_wrestler_pool_number(@weight.pools, wrestler.bracket_line)
|
||||
wrestler.save
|
||||
end
|
||||
persist_pool_numbers(wrestlers_to_update) if persist
|
||||
wrestlers_to_update
|
||||
end
|
||||
|
||||
def get_wrestler_pool_number(number_of_pools, wrestler_seed)
|
||||
@@ -36,4 +38,20 @@ class GeneratePoolNumbers
|
||||
|
||||
pool
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def persist_pool_numbers(wrestlers)
|
||||
return if wrestlers.blank?
|
||||
|
||||
timestamp = Time.current
|
||||
rows = wrestlers.map do |w|
|
||||
{
|
||||
id: w.id,
|
||||
pool: w.pool,
|
||||
updated_at: timestamp
|
||||
}
|
||||
end
|
||||
Wrestler.upsert_all(rows)
|
||||
end
|
||||
end
|
||||
@@ -54,29 +54,20 @@ class CalculateWrestlerTeamScore
|
||||
def byePoints
|
||||
points = 0
|
||||
if @tournament.tournament_type == "Pool to bracket"
|
||||
if @wrestler.pool_wins.size >= 1 and @wrestler.has_a_pool_bye == true
|
||||
if pool_bye_points_eligible?
|
||||
points += 2
|
||||
end
|
||||
end
|
||||
if @tournament.tournament_type.include? "Regular Double Elimination"
|
||||
if @wrestler.championship_advancement_wins.size > 0 or @wrestler.matches_won.select{|m| m.bracket_position == "1/2" and m.win_type != "BYE"}.size > 0
|
||||
# if they have a win in the championship round or if they got a bye all the way to finals and won the finals
|
||||
points += @wrestler.championship_byes.size * 2
|
||||
if @tournament.tournament_type.include? "Double Elimination"
|
||||
if @wrestler.championship_advancement_wins.any? &&
|
||||
@wrestler.championship_byes.any? &&
|
||||
any_bye_round_had_wrestled_match?(@wrestler.championship_byes)
|
||||
points += 2
|
||||
end
|
||||
if @wrestler.consolation_advancement_wins.size > 0 or @wrestler.matches_won.select{|m| m.bracket_position == "3/4" and m.win_type != "BYE"}.size > 0
|
||||
# if they have a win in the consolation round or if they got a bye all the way to 3rd/4th match and won
|
||||
points += @wrestler.consolation_byes.size * 1
|
||||
end
|
||||
end
|
||||
if @tournament.tournament_type.include? "Modified 16 Man Double Elimination"
|
||||
if @wrestler.championship_advancement_wins.size > 0 or @wrestler.matches_won.select{|m| m.bracket_position == "1/2" and m.win_type != "BYE"}.size > 0
|
||||
# if they have a win in the championship round or if they got a bye all the way to finals and won the finals
|
||||
points += @wrestler.championship_byes.size * 2
|
||||
end
|
||||
if @wrestler.consolation_advancement_wins.size > 0 or @wrestler.matches_won.select{|m| m.bracket_position == "5/6" and m.win_type != "BYE"}.size > 0
|
||||
# if they have a win in the consolation round or if they got a bye all the way to 5th/6th match and won
|
||||
# since the consolation bracket goes to 5/6 in a modified tournament
|
||||
points += @wrestler.consolation_byes.size * 1
|
||||
if @wrestler.consolation_advancement_wins.any? &&
|
||||
@wrestler.consolation_byes.any? &&
|
||||
any_bye_round_had_wrestled_match?(@wrestler.consolation_byes)
|
||||
points += 1
|
||||
end
|
||||
end
|
||||
return points
|
||||
@@ -86,4 +77,30 @@ class CalculateWrestlerTeamScore
|
||||
(@wrestler.pin_wins.size * 2) + (@wrestler.tech_wins.size * 1.5) + (@wrestler.major_wins.size * 1)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pool_bye_points_eligible?
|
||||
return false unless @wrestler.pool_wins.size >= 1
|
||||
return false unless @wrestler.weight.pools.to_i > 1
|
||||
|
||||
wrestler_pool_size = @wrestler.weight.wrestlers_in_pool(@wrestler.pool).size
|
||||
largest_pool_size = (1..@wrestler.weight.pools).map { |pool_number| @wrestler.weight.wrestlers_in_pool(pool_number).size }.max
|
||||
|
||||
wrestler_pool_size < largest_pool_size
|
||||
end
|
||||
|
||||
def any_bye_round_had_wrestled_match?(bye_matches)
|
||||
bye_matches.any? do |bye_match|
|
||||
next false if bye_match.round.nil?
|
||||
|
||||
@wrestler.weight.matches.any? do |match|
|
||||
next false if match.id == bye_match.id
|
||||
next false if match.round != bye_match.round
|
||||
next false if match.is_consolation_match != bye_match.is_consolation_match
|
||||
|
||||
match.finished == 1 && match.win_type.present? && match.win_type != "BYE"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -18,8 +18,19 @@
|
||||
|
||||
<div id="cable-status-indicator" data-match-data-target="statusIndicator" class="alert alert-secondary" style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"></div>
|
||||
<h4>Bout <strong><%= @match.bout_number %></strong></h4>
|
||||
<% if @show_next_bout_button && @next_match %>
|
||||
<%= link_to "Skip to Next Match for Mat #{@mat.name}", mat_path(@mat, bout_number: @next_match.bout_number), class: "btn btn-primary" %>
|
||||
<% if @mat %>
|
||||
<% queue_matches = @queue_matches || @mat.queue_matches %>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<% queue_matches.each_with_index do |queue_match, index| %>
|
||||
<% queue_label = "Queue #{index + 1}" %>
|
||||
<% if queue_match %>
|
||||
<% button_class = queue_match.id == @match.id ? "btn btn-success btn-sm" : "btn btn-primary btn-sm" %>
|
||||
<%= link_to "#{queue_label}: Bout #{queue_match.bout_number}", mat_path(@mat, bout_number: queue_match.bout_number), class: button_class %>
|
||||
<% else %>
|
||||
<button type="button" class="btn btn-default btn-sm" disabled><%= "#{queue_label}: Not assigned" %></button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<h4>Bracket Position: <strong><%= @match.bracket_position %></strong></h4>
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<% @mat = mat %>
|
||||
<% @match = local_assigns[:match] || mat.queue1_match %>
|
||||
<% @next_match = local_assigns[:next_match] || mat.queue2_match %>
|
||||
<% @queue_matches = local_assigns[:queue_matches] || mat.queue_matches %>
|
||||
<% @match = local_assigns[:match] || @queue_matches[0] %>
|
||||
<% @match ||= @queue_matches[0] %>
|
||||
<% @next_match = local_assigns[:next_match] || @queue_matches[1] %>
|
||||
<% @show_next_bout_button = local_assigns.key?(:show_next_bout_button) ? local_assigns[:show_next_bout_button] : true %>
|
||||
|
||||
<% @wrestlers = [] %>
|
||||
|
||||
13
app/views/schools/_wrestler_row_cells.html.erb
Normal file
13
app/views/schools/_wrestler_row_cells.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<% if local_assigns[:school_permission_key].present? %>
|
||||
<% wrestler_path_with_key = wrestler_path(wrestler) %>
|
||||
<% wrestler_path_with_key += "?school_permission_key=#{school_permission_key}" %>
|
||||
<td><%= link_to wrestler.name, wrestler_path_with_key %></td>
|
||||
<% else %>
|
||||
<td><%= link_to wrestler.name, wrestler_path(wrestler) %></td>
|
||||
<% end %>
|
||||
<td><%= wrestler.weight.max %></td>
|
||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %> <%= wrestler.criteria %></td>
|
||||
<td><%= wrestler.original_seed %></td>
|
||||
<td><%= wrestler.total_team_points - wrestler.total_points_deducted %></td>
|
||||
<td><%= "Yes" if wrestler.extra? %></td>
|
||||
<td><%= wrestler.next_match_bout_number %> <%= wrestler.next_match_mat_name %></td>
|
||||
@@ -54,19 +54,8 @@
|
||||
<tbody>
|
||||
<% @wrestlers.sort_by { |w| w.weight.max }.each do |wrestler| %>
|
||||
<% if params[:school_permission_key].present? %>
|
||||
<!-- No caching when school_permission_key is present -->
|
||||
<tr>
|
||||
<td>
|
||||
<% wrestler_path_with_key = wrestler_path(wrestler) %>
|
||||
<% wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
|
||||
<%= link_to wrestler.name, wrestler_path_with_key %>
|
||||
</td>
|
||||
<td><%= wrestler.weight.max %></td>
|
||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %> <%= wrestler.criteria %></td>
|
||||
<td><%= wrestler.original_seed %></td>
|
||||
<td><%= wrestler.total_team_points - wrestler.total_points_deducted %></td>
|
||||
<td><%= "Yes" if wrestler.extra? %></td>
|
||||
<td><%= wrestler.next_match_bout_number %> <%= wrestler.next_match_mat_name %></td>
|
||||
<%= render "schools/wrestler_row_cells", wrestler: wrestler, school_permission_key: params[:school_permission_key] %>
|
||||
|
||||
<% if can? :manage, wrestler.school %>
|
||||
<td>
|
||||
@@ -86,34 +75,21 @@
|
||||
<% end %>
|
||||
</tr>
|
||||
<% else %>
|
||||
<!-- Use caching only when school_permission_key is NOT present -->
|
||||
<% cache ["#{wrestler.id}_school_show", @school] do %>
|
||||
<tr>
|
||||
<td><%= link_to wrestler.name, wrestler_path(wrestler) %></td>
|
||||
<td><%= wrestler.weight.max %></td>
|
||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %> <%= wrestler.criteria %></td>
|
||||
<td><%= wrestler.original_seed %></td>
|
||||
<td><%= wrestler.total_team_points - wrestler.total_points_deducted %></td>
|
||||
<td><%= "Yes" if wrestler.extra? %></td>
|
||||
<td><%= wrestler.next_match_bout_number %> <%= wrestler.next_match_mat_name %></td>
|
||||
<% end %>
|
||||
<% if can? :manage, wrestler.school %>
|
||||
<td>
|
||||
<% edit_wrestler_path_with_key = edit_wrestler_path(wrestler) %>
|
||||
<% edit_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
|
||||
|
||||
<% delete_wrestler_path_with_key = wrestler_path(wrestler) %>
|
||||
<% delete_wrestler_path_with_key += "?school_permission_key=#{params[:school_permission_key]}" if params[:school_permission_key].present? %>
|
||||
|
||||
<%= link_to edit_wrestler_path_with_key, class: "text-decoration-none" do %>
|
||||
<span class="fas fa-edit" aria-hidden="true"></span>
|
||||
<% end %>
|
||||
<%= link_to delete_wrestler_path_with_key, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "text-decoration-none" do %>
|
||||
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<tr>
|
||||
<% cache ["school_show_wrestler_cells", wrestler] do %>
|
||||
<%= render "schools/wrestler_row_cells", wrestler: wrestler %>
|
||||
<% end %>
|
||||
<% if can? :manage, wrestler.school %>
|
||||
<td>
|
||||
<%= link_to edit_wrestler_path(wrestler), class: "text-decoration-none" do %>
|
||||
<span class="fas fa-edit" aria-hidden="true"></span>
|
||||
<% end %>
|
||||
<%= link_to wrestler_path(wrestler), data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? This will delete all of his matches." }, class: "text-decoration-none" do %>
|
||||
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<li>Win by major: 1pt extra</li>
|
||||
<li>Win by tech fall: 1.5pt extra</li>
|
||||
<li>Win by fall, default, dq: 2pt extra</li>
|
||||
<li>BYE points: 2pt (if you win at least 1 match in a pool with a BYE)</li>
|
||||
<li>BYE points: 2pt (if you win at least 1 match in a pool with a BYE). - This only applies if your pool has more BYEs than other pools in your bracket. This does not apply to weight classes with 1 pool.</li>
|
||||
</ul>
|
||||
<p>See placement points below (based on the largest bracket of the tournament)</p>
|
||||
<h4>Pool Types</h4>
|
||||
@@ -71,7 +71,7 @@
|
||||
<li>Win by major: 1pt extra</li>
|
||||
<li>Win by tech: 1.5pt extra</li>
|
||||
<li>Win by fall, default, dq, etc: 2pt extra</li>
|
||||
<li>BYE points: 2pts if you have a bye in the championship bracket and win the next match. 1pt if you have a bye in the consolation bracket and win the next match.</li>
|
||||
<li>BYE points: 2pts if you have a bye in the championship bracket and win the next match. 1pt if you have a bye in the consolation bracket and win the next match. - This only applies if you received a bye in a round with at least 1 match in your backet.</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>Modified 16 Man Double Elimination Information</h3>
|
||||
@@ -142,7 +142,7 @@
|
||||
<br>
|
||||
<h3>Future Plans</h3>
|
||||
<br>
|
||||
<p>Future development plans to support 32 and 64 man regulard double elimination, modified (5 per day match rule) 32 man double elimination, and true second double elimination brackets are underway.</p>
|
||||
<p>Future development plans are underway to make the application more flexible, make changes after weigh ins easier, and to add functionality for a live scoreboard.</p>
|
||||
<br>
|
||||
<h3>Contact</h3>
|
||||
<br>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<% if @tournaments.size > 0 %>
|
||||
<h3>My Tournaments</h3>
|
||||
<script>
|
||||
// $(document).ready(function() {
|
||||
// $('#tournamentList').dataTable();
|
||||
// pagingType: "bootstrap";
|
||||
// } );
|
||||
</script>
|
||||
<table class="table table-hover" id="tournamentList">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<% @final_match.each do |match| %>
|
||||
<div class="round">
|
||||
<div class="game">
|
||||
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
||||
<% if params[:print] %>
|
||||
<div class="bout-number"><p><%= match.bout_number %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div>
|
||||
<% else %>
|
||||
<div class="bout-number"><p><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div>
|
||||
<% end %>
|
||||
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<% cache ["bracket_final_match", match, match.wrestler1, match.wrestler2, @winner_place, params[:print].to_s] do %>
|
||||
<div class="round">
|
||||
<div class="game">
|
||||
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
||||
<% if params[:print] %>
|
||||
<div class="bout-number"><p><%= match.bout_number %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div>
|
||||
<% else %>
|
||||
<div class="bout-number"><p><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %></p><p><%= @winner_place %> Place Winner</p></div>
|
||||
<% end %>
|
||||
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
<style>
|
||||
table.smallText tr td { font-size: 10px; }
|
||||
table.smallText {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.smallText th,
|
||||
table.smallText td {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
/*
|
||||
* Bracket Layout Specifics
|
||||
*/
|
||||
.bracket {
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
font-size: 10.5px;
|
||||
gap: 2px;
|
||||
}
|
||||
.game {
|
||||
min-width: 150px;
|
||||
min-height: 50px;
|
||||
min-height: 58px;
|
||||
/*background-color: #ddd;*/
|
||||
border: 1px solid #000; /* Dark border so boxes stay visible when printed */
|
||||
margin: 5px;
|
||||
border: 1.5px solid #000; /* Dark border so boxes stay visible when printed */
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
/*.game:after {
|
||||
@@ -56,14 +64,15 @@ table.smallText tr td { font-size: 10px; }
|
||||
}
|
||||
|
||||
.game-top {
|
||||
border-bottom:1px solid #000;
|
||||
padding: 2px;
|
||||
min-height: 12px;
|
||||
border-bottom:1.5px solid #000;
|
||||
padding: 3px 4px;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
.bout-number {
|
||||
text-align: center;
|
||||
/*padding-top: 15px;*/
|
||||
line-height: 1.35;
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
/* Style links within bout-number like default links */
|
||||
@@ -77,15 +86,29 @@ table.smallText tr td { font-size: 10px; }
|
||||
}
|
||||
|
||||
.bracket-winner {
|
||||
border-bottom:1px solid #000;
|
||||
padding: 2px;
|
||||
min-height: 12px;
|
||||
border-bottom:1.5px solid #000;
|
||||
padding: 3px 4px;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
.game-bottom {
|
||||
border-top:1px solid #000;
|
||||
padding: 2px;
|
||||
min-height: 12px;
|
||||
border-top:1.5px solid #000;
|
||||
padding: 3px 4px;
|
||||
min-height: 16px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.game,
|
||||
.game-top,
|
||||
.game-bottom,
|
||||
.bracket-winner,
|
||||
table.smallText,
|
||||
table.smallText th,
|
||||
table.smallText td {
|
||||
border-color: #000 !important;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<% if @tournament.tournament_type == "Pool to bracket" %>
|
||||
@@ -95,8 +118,6 @@ table.smallText tr td { font-size: 10px; }
|
||||
<table class='smallText'>
|
||||
<tr>
|
||||
<td valign="top" style="padding: 10px;">
|
||||
<% @matches = @tournament.matches.select{|m| m.weight_id == @weight.id} %>
|
||||
<% @wrestlers = Wrestler.where(weight_id: @weight.id) %>
|
||||
<% @pools = @weight.pool_rounds(@matches) %>
|
||||
<%= render 'pool' %>
|
||||
</td>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<div class="round">
|
||||
<% @round_matches.sort_by{|m| m.bracket_position_number}.each do |match| %>
|
||||
<div class="game">
|
||||
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
||||
<% if params[:print] %>
|
||||
<div class="bout-number"><%= match.bout_number %> <%= match.bracket_score_string %> </div>
|
||||
<% else %>
|
||||
<div class="bout-number"><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %> </div>
|
||||
<% end %>
|
||||
<div class="bout-number">Round <%= match.round %></div>
|
||||
<div class="bout-number"><%= match.bracket_position %></div>
|
||||
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
||||
</div>
|
||||
<% cache ["bracket_round_match", match, match.wrestler1, match.wrestler2, params[:print].to_s] do %>
|
||||
<div class="game">
|
||||
<div class="game-top "><%= match.w1_bracket_name.html_safe %> <span></span></div>
|
||||
<% if params[:print] %>
|
||||
<div class="bout-number"><%= match.bout_number %> <%= match.bracket_score_string %> </div>
|
||||
<% else %>
|
||||
<div class="bout-number"><%= link_to match.bout_number, spectate_match_path(match) %> <%= match.bracket_score_string %> </div>
|
||||
<% end %>
|
||||
<div class="bout-number">Round <%= match.round %></div>
|
||||
<div class="bout-number"><%= match.bracket_position %></div>
|
||||
<div class="game-bottom "><%= match.w2_bracket_name.html_safe %><span></span></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
6
app/views/tournaments/_team_score_row.html.erb
Normal file
6
app/views/tournaments/_team_score_row.html.erb
Normal file
@@ -0,0 +1,6 @@
|
||||
<% cache ["team_score_row", school, rank] do %>
|
||||
<tr>
|
||||
<td><%= rank %>. <%= school.name %> (<%= school.abbreviation %>)</td>
|
||||
<td><%= school.page_score_string %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
38
app/views/tournaments/_up_matches_board.html.erb
Normal file
38
app/views/tournaments/_up_matches_board.html.erb
Normal file
@@ -0,0 +1,38 @@
|
||||
<div id="up_matches_board">
|
||||
<h3>Upcoming Matches</h3>
|
||||
<table class="table table-striped table-bordered table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mat </th>
|
||||
<th>On Mat</th>
|
||||
<th>On Deck</th>
|
||||
<th>In The Hole</th>
|
||||
<th>Warm Up</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% (local_assigns[:mats] || tournament.up_matches_mats).each do |m| %>
|
||||
<%= render "tournaments/up_matches_mat_row", mat: m %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<h3>Matches not assigned</h3>
|
||||
<br>
|
||||
<table class="table table-striped table-bordered table-condensed" id="matchList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Round</th>
|
||||
<th>Bout Number</th>
|
||||
<th>Weight Class</th>
|
||||
<th>Matchup</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<%= render partial: "tournaments/up_matches_unassigned_row", collection: (local_assigns[:matches] || tournament.up_matches_unassigned_matches), as: :match %>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
</div>
|
||||
35
app/views/tournaments/_up_matches_mat_row.html.erb
Normal file
35
app/views/tournaments/_up_matches_mat_row.html.erb
Normal file
@@ -0,0 +1,35 @@
|
||||
<% queue1_match, queue2_match, queue3_match, queue4_match = mat.queue_matches %>
|
||||
<% queue_match_dependencies = [queue1_match, queue2_match, queue3_match, queue4_match].compact.flat_map { |match| [match, match.wrestler1, match.wrestler2] } %>
|
||||
<% cache ["up_matches_mat_row", mat, *queue_match_dependencies] do %>
|
||||
<tr>
|
||||
<td><%= mat.name %></td>
|
||||
<td>
|
||||
<% if queue1_match %><strong><%= queue1_match.bout_number %></strong> (<%= queue1_match.bracket_position %>)<br>
|
||||
<%= queue1_match.weight_max %> lbs
|
||||
<br><%= queue1_match.w1_bracket_name %> vs. <br>
|
||||
<%= queue1_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if queue2_match %><strong><%= queue2_match.bout_number %></strong> (<%= queue2_match.bracket_position %>)<br>
|
||||
<%= queue2_match.weight_max %> lbs
|
||||
<br><%= queue2_match.w1_bracket_name %> vs. <br>
|
||||
<%= queue2_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if queue3_match %><strong><%= queue3_match.bout_number %></strong> (<%= queue3_match.bracket_position %>)<br>
|
||||
<%= queue3_match.weight_max %> lbs
|
||||
<br><%= queue3_match.w1_bracket_name %> vs. <br>
|
||||
<%= queue3_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if queue4_match %><strong><%= queue4_match.bout_number %></strong> (<%= queue4_match.bracket_position %>)<br>
|
||||
<%= queue4_match.weight_max %> lbs
|
||||
<br><%= queue4_match.w1_bracket_name %> vs. <br>
|
||||
<%= queue4_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
@@ -0,0 +1,8 @@
|
||||
<% cache ["up_matches_unassigned_row", match, match.wrestler1, match.wrestler2] do %>
|
||||
<tr>
|
||||
<td>Round <%= match.round %></td>
|
||||
<td><%= match.bout_number %></td>
|
||||
<td><%= match.weight_max %></td>
|
||||
<td><%= match.w1_bracket_name %> vs. <%= match.w2_bracket_name %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
@@ -1,20 +1,20 @@
|
||||
<style>
|
||||
/* General styles for pages */
|
||||
@page {
|
||||
margin: 0.5in; /* Universal margin for all pages */
|
||||
margin: 0.35in;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 7.5in; /* Portrait width (8.5in - margins) */
|
||||
height: 10in; /* Portrait height (11in - margins) */
|
||||
width: 7.8in; /* 8.5in - 2 * 0.35in */
|
||||
height: 10.3in; /* 11in - 2 * 0.35in */
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-landscape {
|
||||
width: 10in; /* Landscape width (11in - margins) */
|
||||
height: 7.5in; /* Landscape height (8.5in - margins) */
|
||||
width: 10.3in; /* 11in - 2 * 0.35in */
|
||||
height: 7.8in; /* 8.5in - 2 * 0.35in */
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@@ -26,6 +26,11 @@
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.bracket-container h4 {
|
||||
margin-top: 0.15rem;
|
||||
margin-bottom: 0.45rem;
|
||||
}
|
||||
|
||||
/* Print-specific styles */
|
||||
@media print {
|
||||
/* Set orientation for portrait pages */
|
||||
@@ -51,6 +56,10 @@
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.bracket {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Optional: Hide elements not needed in print */
|
||||
.no-print {
|
||||
display: none;
|
||||
@@ -62,15 +71,10 @@
|
||||
function scaleContent() {
|
||||
document.querySelectorAll('.page, .page-landscape').forEach(page => {
|
||||
const container = page.querySelector('.bracket-container');
|
||||
const isLandscape = page.classList.contains('page-landscape');
|
||||
|
||||
// Page dimensions (1 inch = 96px)
|
||||
const pageWidth = isLandscape ? 10 * 96 : 7.5 * 96;
|
||||
const pageHeight = isLandscape ? 7.5 * 96 : 10 * 96;
|
||||
|
||||
// Subtract margins (0.5 inch margin)
|
||||
const availableWidth = pageWidth - (0.5 * 96 * 2);
|
||||
const availableHeight = pageHeight - (0.5 * 96 * 2);
|
||||
// Use the actual page box size (already accounts for @page margins)
|
||||
const availableWidth = page.clientWidth;
|
||||
const availableHeight = page.clientHeight;
|
||||
|
||||
// Measure content dimensions
|
||||
const contentWidth = container.scrollWidth;
|
||||
@@ -80,8 +84,8 @@
|
||||
const scaleX = availableWidth / contentWidth;
|
||||
const scaleY = availableHeight / contentHeight;
|
||||
|
||||
// Use a slightly relaxed scaling to avoid over-aggressive shrinking
|
||||
const scale = Math.min(scaleX, scaleY, 1); // Ensure scale does not exceed 100% (1)
|
||||
// Keep a tiny buffer so borders/text don't clip at print edges
|
||||
const scale = Math.min(scaleX, scaleY, 1) * 0.99;
|
||||
|
||||
// Apply the scale
|
||||
container.style.transform = `scale(${scale})`;
|
||||
@@ -91,9 +95,9 @@
|
||||
const scaledWidth = contentWidth * scale;
|
||||
const scaledHeight = contentHeight * scale;
|
||||
|
||||
// Center the content within the page
|
||||
const horizontalPadding = (pageWidth - scaledWidth) / 2;
|
||||
const verticalPadding = (pageHeight - scaledHeight) / 2;
|
||||
// Center the content within the available page box
|
||||
const horizontalPadding = (availableWidth - scaledWidth) / 2;
|
||||
const verticalPadding = (availableHeight - scaledHeight) / 2;
|
||||
|
||||
// Apply margin adjustments
|
||||
container.style.marginLeft = `${Math.max(0, horizontalPadding)}px`;
|
||||
@@ -119,14 +123,15 @@
|
||||
<% @weights.sort_by{|w| w.max}.each do |weight| %>
|
||||
<% if @tournament.tournament_type == "Pool to bracket" %>
|
||||
<!-- Need to define what the tournaments#bracket controller defines -->
|
||||
<% @matches = @tournament.matches.select{|m| m.weight_id == weight.id} %>
|
||||
<% @wrestlers = Wrestler.where(weight_id: weight.id) %>
|
||||
<% @matches = @matches_by_weight_id[weight.id] || [] %>
|
||||
<% @wrestlers = @wrestlers_by_weight_id[weight.id] || [] %>
|
||||
<% @pools = weight.pool_rounds(@matches) %>
|
||||
<% @weight = weight %>
|
||||
<%= render 'bracket_partial' %>
|
||||
<% elsif @tournament.tournament_type.include? "Modified 16 Man Double Elimination" or @tournament.tournament_type.include? "Regular Double Elimination" %>
|
||||
<!-- Need to define what the tournaments#bracket controller defines -->
|
||||
<% @matches = weight.matches %>
|
||||
<% @matches = @matches_by_weight_id[weight.id] || [] %>
|
||||
<% @wrestlers = @wrestlers_by_weight_id[weight.id] || [] %>
|
||||
<% @weight = weight %>
|
||||
<%= render 'bracket_partial' %>
|
||||
<% end %>
|
||||
|
||||
@@ -55,10 +55,10 @@
|
||||
</style>
|
||||
|
||||
<% @matches.each do |match| %>
|
||||
<% if match.w1 && match.w2 %>
|
||||
<% w1 = Wrestler.find(match.w1) %>
|
||||
<% w2 = Wrestler.find(match.w2) %>
|
||||
<% end %>
|
||||
<% w1 = @wrestlers_by_id[match.w1] %>
|
||||
<% w2 = @wrestlers_by_id[match.w2] %>
|
||||
<% w1_name = w1&.name || match.loser1_name %>
|
||||
<% w2_name = w2&.name || match.loser2_name %>
|
||||
|
||||
<div class="pagebreak">
|
||||
<p><strong>Bout Number:</strong> <%= match.bout_number %> <strong>Weight Class:</strong> <%= match.weight.max %> <strong>Round:</strong> <%= match.round %> <strong>Bracket Position:</strong> <%= match.bracket_position %></p>
|
||||
@@ -69,10 +69,10 @@
|
||||
<tr class="small-row">
|
||||
<th class="fixed-width">Circle Winner</th>
|
||||
<th>
|
||||
<p><%= match.w1_name %>-<%= w1&.school&.name %></p>
|
||||
<p><%= w1_name %>-<%= w1&.school&.name %></p>
|
||||
</th>
|
||||
<th>
|
||||
<p><%= match.w2_name %>-<%= w2&.school&.name %></p>
|
||||
<p><%= w2_name %>-<%= w2&.school&.name %></p>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% cache ["#{@weight.id}_bracket", @weight] do %>
|
||||
<% cache ["#{@weight.id}_bracket", @weight, params[:print].to_s] do %>
|
||||
<%= render 'bracket_partial' %>
|
||||
<% end %>
|
||||
<% if @tournament.tournament_type == "Pool to bracket" %>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
<%
|
||||
wrestlers_by_id = @tournament.wrestlers.index_by(&:id)
|
||||
weights_by_id = @tournament.weights.index_by(&:id)
|
||||
mats_by_id = @tournament.mats.index_by(&:id)
|
||||
sorted_matches = @tournament.matches.sort_by(&:bout_number)
|
||||
%>
|
||||
{
|
||||
"tournament": {
|
||||
"attributes": <%= @tournament.attributes.to_json %>,
|
||||
@@ -14,13 +20,13 @@
|
||||
"weight": wrestler.weight&.attributes
|
||||
}
|
||||
) }.to_json %>,
|
||||
"matches": <%= @tournament.matches.sort_by(&:bout_number).map { |match| match.attributes.merge(
|
||||
"matches": <%= sorted_matches.map { |match| match.attributes.merge(
|
||||
{
|
||||
"w1_name": Wrestler.find_by(id: match.w1)&.name,
|
||||
"w2_name": Wrestler.find_by(id: match.w2)&.name,
|
||||
"winner_name": Wrestler.find_by(id: match.winner_id)&.name,
|
||||
"weight": Weight.find_by(id: match.weight_id)&.attributes,
|
||||
"mat": Mat.find_by(id: match.mat_id)&.attributes
|
||||
"w1_name": wrestlers_by_id[match.w1]&.name,
|
||||
"w2_name": wrestlers_by_id[match.w2]&.name,
|
||||
"winner_name": wrestlers_by_id[match.winner_id]&.name,
|
||||
"weight": weights_by_id[match.weight_id]&.attributes,
|
||||
"mat": mats_by_id[match.mat_id]&.attributes
|
||||
}
|
||||
) }.to_json %>
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
|
||||
|
||||
<h1>All <%= @tournament.name %> matches</h1>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#matchesList').dataTable();
|
||||
pagingType: "bootstrap";
|
||||
} );
|
||||
</script>
|
||||
</br>
|
||||
|
||||
<% matches_path = "/tournaments/#{@tournament.id}/matches" %>
|
||||
|
||||
<%= form_tag(matches_path, method: :get, id: "search-form") do %>
|
||||
<%= text_field_tag :search, params[:search], placeholder: "Search wrestler, school, or bout #" %>
|
||||
<%= submit_tag "Search" %>
|
||||
<% end %>
|
||||
|
||||
<p>Search by wrestler name, school name, or bout number.</p>
|
||||
|
||||
<br>
|
||||
<table class="table table-striped table-bordered table-condensed" id="matchesList">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -35,6 +37,49 @@
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<% if @total_pages.present? && @total_pages > 1 %>
|
||||
<nav aria-label="Matches pagination">
|
||||
<ul class="pagination">
|
||||
<% if @page > 1 %>
|
||||
<li class="page-item">
|
||||
<%= link_to "Previous", { controller: "tournaments", action: "matches", id: @tournament.id, page: @page - 1, search: params[:search] }, class: "page-link" %>
|
||||
</li>
|
||||
<% else %>
|
||||
<li class="page-item disabled"><span class="page-link">Previous</span></li>
|
||||
<% end %>
|
||||
|
||||
<% window = 5
|
||||
left = [1, @page - window / 2].max
|
||||
right = [@total_pages, left + window - 1].min
|
||||
left = [1, right - window + 1].max
|
||||
%>
|
||||
<% (left..right).each do |p| %>
|
||||
<% if p == @page %>
|
||||
<li class="page-item active"><span class="page-link"><%= p %></span></li>
|
||||
<% else %>
|
||||
<li class="page-item"><%= link_to p, { controller: "tournaments", action: "matches", id: @tournament.id, page: p, search: params[:search] }, class: "page-link" %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @page < @total_pages %>
|
||||
<li class="page-item">
|
||||
<%= link_to "Next", { controller: "tournaments", action: "matches", id: @tournament.id, page: @page + 1, search: params[:search] }, class: "page-link" %>
|
||||
</li>
|
||||
<% else %>
|
||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<p class="text-muted">
|
||||
<% start_index = ((@page - 1) * @per_page) + 1
|
||||
end_index = [@page * @per_page, @total_count].min
|
||||
%>
|
||||
Showing <%= start_index %> - <%= end_index %> of <%= @total_count %> matches
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<br>
|
||||
<p>Total matches without byes: <%= @matches.select{|m| m.loser1_name != 'BYE' and m.loser2_name != 'BYE'}.size %></p>
|
||||
<p>Unfinished matches: <%= @matches.select{|m| m.finished != 1 and m.loser1_name != 'BYE' and m.loser2_name != 'BYE'}.size %></p>
|
||||
<p>Total matches without byes: <%= @matches_without_byes_count %></p>
|
||||
<p>Unfinished matches: <%= @unfinished_matches_without_byes_count %></p>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<% cache ["#{@tournament.id}_team_scores", @tournament] do %>
|
||||
|
||||
<% team_scores_last_updated = @schools.map(&:updated_at).compact.max&.utc&.to_fs(:nsec) %>
|
||||
<% cache ["team_scores", @tournament.id, @schools.size, team_scores_last_updated] do %>
|
||||
<table class="pagebreak table table-striped table-bordered">
|
||||
<h3>Team Scores</h3>
|
||||
<thead>
|
||||
@@ -11,11 +10,8 @@
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% @schools.each do |school| %>
|
||||
<tr>
|
||||
<td><%= @schools.index(school) + 1 %>. <%= school.name %> (<%= school.abbreviation %>)</td>
|
||||
<td><%= school.page_score_string %></td>
|
||||
</tr>
|
||||
<% @schools.each_with_index do |school, index| %>
|
||||
<%= render "tournaments/team_score_row", school: school, rank: index + 1 %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,107 +1,19 @@
|
||||
<% cache ["#{@tournament.id}_up_matches", @tournament] do %>
|
||||
<script>
|
||||
// $(document).ready(function() {
|
||||
// $('#matchList').dataTable();
|
||||
// } );
|
||||
</script>
|
||||
<script>
|
||||
const setUpMatchesRefresh = () => {
|
||||
if (window.__upMatchesRefreshTimeout) {
|
||||
clearTimeout(window.__upMatchesRefreshTimeout);
|
||||
}
|
||||
window.__upMatchesRefreshTimeout = setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
}, 30000);
|
||||
};
|
||||
<div data-controller="up-matches-connection">
|
||||
<% if params[:print] != "true" %>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<%= link_to "Show Bout Board in Full Screen", up_matches_path(@tournament, print: true), class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
document.addEventListener("turbo:load", setUpMatchesRefresh);
|
||||
// turbo:before-cache stops the timer refresh from occurring if you navigate away from up_matches
|
||||
document.addEventListener("turbo:before-cache", () => {
|
||||
if (window.__upMatchesRefreshTimeout) {
|
||||
clearTimeout(window.__upMatchesRefreshTimeout);
|
||||
window.__upMatchesRefreshTimeout = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<br>
|
||||
<br>
|
||||
<h5 style="color:red">This page reloads every 30s</h5>
|
||||
<br>
|
||||
<h3>Upcoming Matches</h3>
|
||||
<br>
|
||||
<table class="table table-striped table-bordered table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mat </th>
|
||||
<th>On Mat</th>
|
||||
<th>On Deck</th>
|
||||
<th>In The Hole</th>
|
||||
<th>Warm Up</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<%= turbo_stream_from @tournament, data: { up_matches_connection_target: "stream" } %>
|
||||
<div
|
||||
id="up-matches-cable-status-indicator"
|
||||
data-up-matches-connection-target="statusIndicator"
|
||||
class="alert alert-secondary"
|
||||
style="padding: 5px; margin-bottom: 10px; border-radius: 4px;"
|
||||
>
|
||||
Connecting to server for real-time up matches updates...
|
||||
</div>
|
||||
|
||||
<tbody>
|
||||
<% @mats.each.map do |m| %>
|
||||
<tr>
|
||||
<td><%= m.name %></td>
|
||||
<td>
|
||||
<% if m.queue1_match %><strong><%=m.queue1_match.bout_number%></strong> (<%= m.queue1_match.bracket_position %>)<br>
|
||||
<%= m.queue1_match.weight_max %> lbs
|
||||
<br><%= m.queue1_match.w1_bracket_name %> vs. <br>
|
||||
<%= m.queue1_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if m.queue2_match %><strong><%=m.queue2_match.bout_number%></strong> (<%= m.queue2_match.bracket_position %>)<br>
|
||||
<%= m.queue2_match.weight_max %> lbs
|
||||
<br><%= m.queue2_match.w1_bracket_name %> vs. <br>
|
||||
<%= m.queue2_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if m.queue3_match %><strong><%=m.queue3_match.bout_number%></strong> (<%= m.queue3_match.bracket_position %>)<br>
|
||||
<%= m.queue3_match.weight_max %> lbs
|
||||
<br><%= m.queue3_match.w1_bracket_name %> vs. <br>
|
||||
<%= m.queue3_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if m.queue4_match %><strong><%=m.queue4_match.bout_number%></strong> (<%= m.queue4_match.bracket_position %>)<br>
|
||||
<%= m.queue4_match.weight_max %> lbs
|
||||
<br><%= m.queue4_match.w1_bracket_name %> vs. <br>
|
||||
<%= m.queue4_match.w2_bracket_name %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
<h3>Matches not assigned</h3>
|
||||
<br>
|
||||
<table class="table table-striped table-bordered table-condensed" id="matchList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Round</th>
|
||||
<th>Bout Number</th>
|
||||
<th>Weight Class</th>
|
||||
<th>Matchup</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% if @matches.size > 0 %>
|
||||
<% @matches.each.map do |m| %>
|
||||
<tr>
|
||||
<td>Round <%= m.round %></td>
|
||||
<td><%= m.bout_number %></td>
|
||||
<td><%= m.weight_max %></td>
|
||||
<td><%= m.w1_bracket_name %> vs. <%= m.w2_bracket_name %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<% end %>
|
||||
<%= render "up_matches_board", tournament: @tournament, mats: @mats, matches: @matches %>
|
||||
</div>
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
height: 1in;
|
||||
}
|
||||
</style>
|
||||
<% @tournament.schools.each do |school| %>
|
||||
<% @schools.each do |school| %>
|
||||
<table class="table table-striped table-bordered table-condensed pagebreak">
|
||||
<h5><%= school.name %></h4>
|
||||
<p><strong>Weigh In Ref:</strong> <%= @tournament.weigh_in_ref %></p>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@@ -27,7 +28,7 @@
|
||||
<tr>
|
||||
<td><%= wrestler.name %></td>
|
||||
<td><%= wrestler.weight.max %></td>
|
||||
<td></td>
|
||||
<td><%= wrestler.offical_weight %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= form_tag @wrestlers_update_path do %>
|
||||
<%= form_tag "/tournaments/#{@tournament.id}/weigh_in/#{@weight.id}", method: :post do %>
|
||||
<% @wrestlers.order("original_seed asc").each do |wrestler| %>
|
||||
<% if wrestler.weight_id == @weight.id %>
|
||||
<tr>
|
||||
@@ -19,7 +19,7 @@
|
||||
<td><%= wrestler.weight.max %></td>
|
||||
<td>
|
||||
<% if user_signed_in? %>
|
||||
<%= fields_for "wrestler[]", wrestler do |w| %>
|
||||
<%= fields_for "wrestler[#{wrestler.id}]", wrestler do |w| %>
|
||||
<%= w.number_field :offical_weight, :step => 'any' %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
||||
10
app/views/weights/_readonly_wrestler_row.html.erb
Normal file
10
app/views/weights/_readonly_wrestler_row.html.erb
Normal file
@@ -0,0 +1,10 @@
|
||||
<% cache ["weight_show_wrestler_row", wrestler] do %>
|
||||
<tr>
|
||||
<td><%= link_to wrestler.name, wrestler %></td>
|
||||
<td><%= wrestler.school.name %></td>
|
||||
<td><%= wrestler.original_seed %></td>
|
||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
|
||||
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
|
||||
<td><%= "Yes" if wrestler.extra? %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
@@ -14,35 +14,36 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @wrestlers.sort_by{|w| [w.original_seed ? 0 : 1, w.original_seed || 0]}.each do |wrestler| %>
|
||||
<% if wrestler.weight_id == @weight.id %>
|
||||
<tr>
|
||||
<td><%= link_to "#{wrestler.name}", wrestler %></td>
|
||||
<td><%= wrestler.school.name %></td>
|
||||
<td>
|
||||
<% if can? :manage, @tournament %>
|
||||
<%= fields_for "wrestler[]", wrestler do |w| %>
|
||||
<%= w.text_field :original_seed %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= wrestler.original_seed %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
|
||||
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
|
||||
<td><% if wrestler.extra? == true %>
|
||||
Yes
|
||||
<% end %></td>
|
||||
<% if can? :manage, @tournament %>
|
||||
<td>
|
||||
<%= link_to wrestler, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? THIS WILL DELETE ALL MATCHES." }, class: "text-decoration-none" do %>
|
||||
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
||||
<% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% sorted_wrestlers = @wrestlers.sort_by{|w| [w.original_seed ? 0 : 1, w.original_seed || 0]} %>
|
||||
<% if can? :manage, @tournament %>
|
||||
<% sorted_wrestlers.each do |wrestler| %>
|
||||
<% if wrestler.weight_id == @weight.id %>
|
||||
<tr>
|
||||
<td><%= link_to wrestler.name, wrestler %></td>
|
||||
<td><%= wrestler.school.name %></td>
|
||||
<td>
|
||||
<%= fields_for "wrestler[]", wrestler do |w| %>
|
||||
<%= w.text_field :original_seed %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td><%= wrestler.season_win %>-<%= wrestler.season_loss %></td>
|
||||
<td><%= wrestler.criteria %> Win <%= wrestler.season_win_percentage %>%</td>
|
||||
<td><%= "Yes" if wrestler.extra? %></td>
|
||||
<td>
|
||||
<%= link_to wrestler, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete #{wrestler.name}? THIS WILL DELETE ALL MATCHES." }, class: "text-decoration-none" do %>
|
||||
<span class="fas fa-trash-alt" aria-hidden="true"></span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<% sorted_wrestlers.each do |wrestler| %>
|
||||
<% if wrestler.weight_id == @weight.id %>
|
||||
<%= render "weights/readonly_wrestler_row", wrestler: wrestler %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<br><p>*All wrestlers without a seed (determined by tournament director) will be assigned a random bracket line.</p>
|
||||
|
||||
@@ -25,9 +25,6 @@ module Wrestling
|
||||
config.active_record.schema_format = :ruby
|
||||
config.active_record.dump_schemas = :all
|
||||
|
||||
# Fix deprecation warning for to_time in Rails 8.1
|
||||
config.active_support.to_time_preserves_timezone = :zone
|
||||
|
||||
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
||||
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
||||
# Common ones are `templates`, `generators`, or `middleware`, for example.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Async adapter only works within the same process, so for manually triggering cable updates from a console,
|
||||
# and seeing results in the browser, you must do so from the web console (running inside the dev process),
|
||||
# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view
|
||||
# to make the web console appear.
|
||||
development:
|
||||
adapter: async
|
||||
adapter: solid_cable
|
||||
connects_to:
|
||||
database:
|
||||
writing: cable
|
||||
polling_interval: 0.1.seconds
|
||||
message_retention: 1.day
|
||||
|
||||
test:
|
||||
adapter: test
|
||||
|
||||
@@ -11,9 +11,8 @@ pin "@rails/actioncable", to: "actioncable.esm.js" # For Action Cable
|
||||
# and pin it directly, e.g., pin "jquery", to: "jquery.min.js"
|
||||
pin "jquery", to: "jquery.js"
|
||||
|
||||
# Pin Bootstrap and DataTables from vendor/assets/javascripts/
|
||||
# Pin Bootstrap from vendor/assets/javascripts/
|
||||
pin "bootstrap", to: "bootstrap.min.js"
|
||||
pin "datatables.net", to: "jquery.dataTables.min.js" # Assuming this is how you want to import it
|
||||
|
||||
# If Bootstrap requires Popper.js, and you have it in vendor/assets/javascripts/
|
||||
# pin "@popperjs/core", to: "popper.min.js" # Or the actual filename if different
|
||||
|
||||
@@ -216,4 +216,20 @@ class MatAssignmentRulesControllerTest < ActionController::TestCase
|
||||
assert_equal ['A1', 'B2'], rule.bracket_positions
|
||||
assert_equal [1, 2], rule.rounds
|
||||
end
|
||||
|
||||
test "index lists created mat assignment rule once in html" do
|
||||
sign_in_owner
|
||||
unique_mat = Mat.create!(name: "Unique Mat #{SecureRandom.hex(4)}", tournament_id: @tournament.id)
|
||||
MatAssignmentRule.create!(
|
||||
mat_id: unique_mat.id,
|
||||
tournament_id: @tournament.id,
|
||||
weight_classes: [1],
|
||||
bracket_positions: ['1/2'],
|
||||
rounds: [2]
|
||||
)
|
||||
|
||||
index
|
||||
assert_response :success
|
||||
assert_equal 1, response.body.scan(unique_mat.name).size
|
||||
end
|
||||
end
|
||||
|
||||
@@ -259,6 +259,62 @@ class MatsControllerTest < ActionController::TestCase
|
||||
assert_match /#{bout_number}/, response.body, "The bout_number should be rendered on the page"
|
||||
end
|
||||
|
||||
test "mat show renders queue buttons for all four queue slots" do
|
||||
sign_in_owner
|
||||
|
||||
available_matches = @tournament.matches.where(mat_id: nil).limit(3).to_a
|
||||
@mat.assign_match_to_queue!(available_matches[0], 2) if available_matches[0]
|
||||
@mat.assign_match_to_queue!(available_matches[1], 3) if available_matches[1]
|
||||
@mat.assign_match_to_queue!(available_matches[2], 4) if available_matches[2]
|
||||
|
||||
get :show, params: { id: @mat.id }
|
||||
assert_response :success
|
||||
|
||||
assert_includes response.body, "Queue 1: Bout"
|
||||
assert_includes response.body, "Queue 2:"
|
||||
assert_includes response.body, "Queue 3:"
|
||||
assert_includes response.body, "Queue 4:"
|
||||
end
|
||||
|
||||
test "mat show highlights selected queue button and keeps bout_number links working" do
|
||||
sign_in_owner
|
||||
|
||||
queue2_match = @mat.queue2_match
|
||||
unless queue2_match
|
||||
assignable = @tournament.matches.where(mat_id: nil).first
|
||||
@mat.assign_match_to_queue!(assignable, 2) if assignable
|
||||
queue2_match = @mat.reload.queue2_match
|
||||
end
|
||||
assert queue2_match, "Expected queue2 match to be present"
|
||||
|
||||
get :show, params: { id: @mat.id, bout_number: queue2_match.bout_number, foo: "bar" }
|
||||
assert_response :success
|
||||
|
||||
assert_includes response.body, "Queue 2: Bout #{queue2_match.bout_number}"
|
||||
assert_match(/btn btn-success btn-sm/, response.body)
|
||||
assert_includes response.body, "bout_number=#{queue2_match.bout_number}"
|
||||
end
|
||||
|
||||
test "mat show falls back to queue1 when requested bout number is not currently queued" do
|
||||
sign_in_owner
|
||||
|
||||
queue1 = @mat.reload.queue1_match
|
||||
assert queue1, "Expected queue1 to be present"
|
||||
|
||||
get :show, params: { id: @mat.id, bout_number: 999999 }
|
||||
assert_response :success
|
||||
assert_includes response.body, "Bout <strong>#{queue1.bout_number}</strong>"
|
||||
end
|
||||
|
||||
test "mat show renders no matches assigned when queue is empty" do
|
||||
sign_in_owner
|
||||
|
||||
@mat.clear_queue!
|
||||
get :show, params: { id: @mat.id }
|
||||
assert_response :success
|
||||
assert_includes response.body, "No matches assigned to this mat."
|
||||
end
|
||||
|
||||
test "logged in tournament owner should redirect back to the first unfinished bout on a mat after submitting a match with a bout number param" do
|
||||
sign_in_owner
|
||||
|
||||
@@ -287,11 +343,12 @@ class MatsControllerTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
#TESTS THAT NEED MATCHES PUT ABOVE THIS
|
||||
test "redirect show if no matches" do
|
||||
test "show renders when no matches" do
|
||||
sign_in_owner
|
||||
wipe
|
||||
show
|
||||
no_matches
|
||||
success
|
||||
assert_includes response.body, "No matches assigned to this mat."
|
||||
end
|
||||
|
||||
# Assign Next Match Permissions
|
||||
|
||||
143
test/controllers/school_show_cache_test.rb
Normal file
143
test/controllers/school_show_cache_test.rb
Normal file
@@ -0,0 +1,143 @@
|
||||
require "test_helper"
|
||||
|
||||
class SchoolShowCacheTest < ActionController::TestCase
|
||||
tests SchoolsController
|
||||
|
||||
setup do
|
||||
create_double_elim_tournament_single_weight_1_6(8)
|
||||
@tournament.update!(user_id: users(:one).id)
|
||||
@school = @tournament.schools.first
|
||||
|
||||
sign_in users(:one)
|
||||
|
||||
@original_perform_caching = ActionController::Base.perform_caching
|
||||
ActionController::Base.perform_caching = true
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
teardown do
|
||||
Rails.cache.clear
|
||||
ActionController::Base.perform_caching = @original_perform_caching
|
||||
end
|
||||
|
||||
test "school show wrestler cell fragments hit cache and invalidate after wrestler update" do
|
||||
first_events = cache_events_for_school_show do
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial school show render to write wrestler cell fragments"
|
||||
|
||||
second_events = cache_events_for_school_show do
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(second_events), "Expected repeat school show render to reuse wrestler cell fragments"
|
||||
assert_operator cache_hits(second_events), :>, 0, "Expected repeat school show render to hit wrestler cell cache"
|
||||
|
||||
wrestler = @school.wrestlers.first
|
||||
third_events = cache_events_for_school_show do
|
||||
wrestler.touch
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(third_events), :>, 0, "Expected wrestler update to invalidate school show wrestler cell cache"
|
||||
end
|
||||
|
||||
test "school show does not leak manage-only controls from cache across users" do
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
assert_includes response.body, "New Wrestler"
|
||||
assert_match(/fa-trash-alt/, response.body)
|
||||
assert_match(/fa-edit/, response.body)
|
||||
|
||||
sign_out
|
||||
|
||||
spectator_events = cache_events_for_school_show do
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_hits(spectator_events), :>, 0, "Expected spectator request to hit wrestler cell cache warmed by owner"
|
||||
assert_not_includes response.body, "New Wrestler"
|
||||
assert_no_match(/fa-trash-alt/, response.body)
|
||||
assert_no_match(/fa-edit/, response.body)
|
||||
end
|
||||
|
||||
test "school show with school_permission_key bypasses cached wrestler cell fragments" do
|
||||
@school.update!(permission_key: SecureRandom.uuid)
|
||||
sign_out
|
||||
|
||||
key_request_events = cache_events_for_school_show do
|
||||
get :show, params: { id: @school.id, school_permission_key: @school.permission_key }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
assert_equal 0, cache_writes(key_request_events), "Expected school_permission_key request to bypass cached wrestler cells"
|
||||
assert_equal 0, cache_hits(key_request_events), "Expected school_permission_key request to avoid reading cached wrestler cells"
|
||||
end
|
||||
|
||||
test "completing a match expires school show wrestler cell caches" do
|
||||
warm_events = cache_events_for_school_show do
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(warm_events), :>, 0, "Expected initial school show render to warm wrestler cell cache"
|
||||
|
||||
wrestler = @school.wrestlers.first
|
||||
assert wrestler, "Expected a wrestler for match-completion cache test"
|
||||
match = wrestler.unfinished_matches.first || wrestler.all_matches.first
|
||||
assert match, "Expected a match involving school wrestler"
|
||||
|
||||
winner_id = match.w1 || match.w2
|
||||
assert winner_id, "Expected match to have at least one wrestler slot"
|
||||
match.update!(
|
||||
finished: 1,
|
||||
winner_id: winner_id,
|
||||
win_type: "Decision",
|
||||
score: "1-0"
|
||||
)
|
||||
|
||||
post_action_events = cache_events_for_school_show do
|
||||
get :show, params: { id: @school.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(post_action_events), :>, 0, "Expected completed match to expire school show wrestler cell cache"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sign_out
|
||||
@request.session[:user_id] = nil
|
||||
@controller.instance_variable_set(:@current_user, nil)
|
||||
@controller.instance_variable_set(:@current_ability, nil)
|
||||
end
|
||||
|
||||
def cache_events_for_school_show
|
||||
events = []
|
||||
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||
key = payload[:key].to_s
|
||||
next unless key.include?("school_show_wrestler_cells")
|
||||
|
||||
events << { name: name, hit: payload[:hit] }
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribed(
|
||||
subscriber,
|
||||
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||
) do
|
||||
yield
|
||||
end
|
||||
|
||||
events
|
||||
end
|
||||
|
||||
def cache_writes(events)
|
||||
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||
end
|
||||
|
||||
def cache_hits(events)
|
||||
events.count do |event|
|
||||
event[:name] == "cache_fetch_hit.active_support" ||
|
||||
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -36,4 +36,41 @@ class StaticPagesControllerTest < ActionController::TestCase
|
||||
get :my_tournaments
|
||||
success
|
||||
end
|
||||
|
||||
test "my_tournaments page lists delegated tournament and delegated school once in html" do
|
||||
user = users(:two)
|
||||
sign_in_non_owner
|
||||
|
||||
delegated_tournament = Tournament.create!(
|
||||
name: "Delegated Tournament #{SecureRandom.hex(4)}",
|
||||
address: "123 Delegate St",
|
||||
director: "Director",
|
||||
director_email: "delegate_tournament_#{SecureRandom.hex(4)}@example.com",
|
||||
tournament_type: "Pool to bracket",
|
||||
date: Date.today,
|
||||
is_public: true
|
||||
)
|
||||
TournamentDelegate.create!(tournament_id: delegated_tournament.id, user_id: user.id)
|
||||
|
||||
school_tournament = Tournament.create!(
|
||||
name: "School Tournament #{SecureRandom.hex(4)}",
|
||||
address: "456 School St",
|
||||
director: "Director",
|
||||
director_email: "delegate_school_#{SecureRandom.hex(4)}@example.com",
|
||||
tournament_type: "Pool to bracket",
|
||||
date: Date.today + 1,
|
||||
is_public: true
|
||||
)
|
||||
delegated_school = School.create!(
|
||||
name: "Delegated School #{SecureRandom.hex(4)}",
|
||||
tournament_id: school_tournament.id
|
||||
)
|
||||
SchoolDelegate.create!(school_id: delegated_school.id, user_id: user.id)
|
||||
|
||||
get :my_tournaments
|
||||
assert_response :success
|
||||
assert_equal 1, response.body.scan(delegated_tournament.name).size
|
||||
assert_equal 1, response.body.scan(delegated_school.name).size
|
||||
assert_equal 1, response.body.scan(school_tournament.name).size
|
||||
end
|
||||
end
|
||||
|
||||
164
test/controllers/tournament_pages_cache_test.rb
Normal file
164
test/controllers/tournament_pages_cache_test.rb
Normal file
@@ -0,0 +1,164 @@
|
||||
require "test_helper"
|
||||
|
||||
class TournamentPagesCacheTest < ActionController::TestCase
|
||||
tests TournamentsController
|
||||
|
||||
setup do
|
||||
create_double_elim_tournament_single_weight_1_6(8)
|
||||
@tournament.update!(user_id: users(:one).id)
|
||||
@weight = @tournament.weights.first
|
||||
|
||||
sign_in users(:one)
|
||||
|
||||
@original_perform_caching = ActionController::Base.perform_caching
|
||||
ActionController::Base.perform_caching = true
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
teardown do
|
||||
Rails.cache.clear
|
||||
ActionController::Base.perform_caching = @original_perform_caching
|
||||
end
|
||||
|
||||
test "team_scores cache hits on repeat render and rewrites after school update" do
|
||||
first_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||
get :team_scores, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial team_scores render to write fragments"
|
||||
|
||||
second_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||
get :team_scores, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(second_events), "Expected repeat team_scores render to reuse fragments"
|
||||
assert_operator cache_hits(second_events), :>, 0, "Expected repeat team_scores render to hit cache"
|
||||
|
||||
school = @tournament.schools.first
|
||||
third_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||
school.update!(score: (school.score || 0) + 1)
|
||||
get :team_scores, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(third_events), :>, 0, "Expected school score update to invalidate team_scores cache"
|
||||
end
|
||||
|
||||
test "bracket cache hits on repeat render and rewrites after match update" do
|
||||
key_markers = [@weight.id.to_s + "_bracket", "bracket_round_match", "bracket_final_match"]
|
||||
|
||||
first_events = cache_events_for(key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial bracket render to write fragments"
|
||||
|
||||
second_events = cache_events_for(key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(second_events), "Expected repeat bracket render to reuse fragments"
|
||||
assert_operator cache_hits(second_events), :>, 0, "Expected repeat bracket render to hit cache"
|
||||
|
||||
match = @weight.matches.first
|
||||
third_events = cache_events_for(key_markers) do
|
||||
match.touch
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(third_events), :>, 0, "Expected match update to invalidate bracket cache"
|
||||
end
|
||||
|
||||
test "bracket cache separates print and non-print variants" do
|
||||
key_markers = [@weight.id.to_s + "_bracket"]
|
||||
|
||||
non_print_events = cache_events_for(key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(non_print_events), :>, 0, "Expected non-print bracket render to write a page fragment"
|
||||
assert_match(%r{\/matches\/\d+\/spectate}, response.body, "Expected non-print bracket view to include spectate links")
|
||||
|
||||
first_print_events = cache_events_for(key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id, print: true }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_print_events), :>, 0, "Expected first print bracket render to write a separate page fragment"
|
||||
assert_no_match(%r{\/matches\/\d+\/spectate}, response.body, "Expected print bracket view to omit spectate links")
|
||||
|
||||
second_print_events = cache_events_for(key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id, print: true }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(second_print_events), "Expected repeat print bracket render to reuse print cache fragment"
|
||||
assert_operator cache_hits(second_print_events), :>, 0, "Expected repeat print bracket render to hit cache"
|
||||
end
|
||||
|
||||
test "completing a match expires team_scores and bracket caches" do
|
||||
team_warm_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||
get :team_scores, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(team_warm_events), :>, 0, "Expected initial team_scores render to warm cache"
|
||||
|
||||
bracket_key_markers = [@weight.id.to_s + "_bracket", "bracket_round_match", "bracket_final_match"]
|
||||
bracket_warm_events = cache_events_for(bracket_key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(bracket_warm_events), :>, 0, "Expected initial bracket render to warm cache"
|
||||
|
||||
match = @weight.matches.where(finished: [nil, 0]).first || @weight.matches.first
|
||||
assert match, "Expected a match to complete for expiration test"
|
||||
|
||||
match.update!(
|
||||
finished: 1,
|
||||
winner_id: match.w1 || match.w2,
|
||||
win_type: "Decision",
|
||||
score: "1-0"
|
||||
)
|
||||
|
||||
team_post_events = cache_events_for(%w[team_scores team_score_row]) do
|
||||
get :team_scores, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(team_post_events), :>, 0, "Expected completed match to expire team_scores cache"
|
||||
|
||||
bracket_post_events = cache_events_for(bracket_key_markers) do
|
||||
get :bracket, params: { id: @tournament.id, weight: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(bracket_post_events), :>, 0, "Expected completed match to expire bracket cache"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_events_for(key_markers)
|
||||
events = []
|
||||
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||
key = payload[:key].to_s
|
||||
next unless key_markers.any? { |marker| key.include?(marker) }
|
||||
|
||||
events << { name: name, hit: payload[:hit] }
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribed(
|
||||
subscriber,
|
||||
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||
) do
|
||||
yield
|
||||
end
|
||||
|
||||
events
|
||||
end
|
||||
|
||||
def cache_writes(events)
|
||||
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||
end
|
||||
|
||||
def cache_hits(events)
|
||||
events.count do |event|
|
||||
event[:name] == "cache_fetch_hit.active_support" ||
|
||||
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -117,6 +117,29 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
sign_in_owner
|
||||
get :weigh_in, params: { id: 1 }
|
||||
success
|
||||
assert_not_includes response.body, "Weights were successfully recorded."
|
||||
end
|
||||
|
||||
test "printable weigh in sheet includes wrestler name school weight class and actual weight" do
|
||||
sign_in_owner
|
||||
@tournament.update!(weigh_in_ref: "Ref Smith")
|
||||
wrestler = @tournament.weights.first.wrestlers.first
|
||||
wrestler.update!(
|
||||
name: "Printable Test Wrestler",
|
||||
offical_weight: 106.4
|
||||
)
|
||||
school = wrestler.school
|
||||
|
||||
get :weigh_in_sheet, params: { id: @tournament.id, print: true }
|
||||
assert_response :success
|
||||
|
||||
assert_includes response.body, school.name
|
||||
assert_includes response.body, "Printable Test Wrestler"
|
||||
assert_includes response.body, wrestler.weight.max.to_s
|
||||
assert_includes response.body, "106.4"
|
||||
assert_includes response.body, "Actual Weight"
|
||||
assert_includes response.body, "Weigh In Ref:"
|
||||
assert_includes response.body, "Ref Smith"
|
||||
end
|
||||
|
||||
test "logged in non tournament owner cannot access weigh_ins" do
|
||||
@@ -155,6 +178,27 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
success
|
||||
end
|
||||
|
||||
test "logged in tournament owner can save wrestler actual weight on weigh in weight page" do
|
||||
sign_in_owner
|
||||
wrestler = @tournament.weights.first.wrestlers.first
|
||||
|
||||
post :weigh_in_weight, params: {
|
||||
id: @tournament.id,
|
||||
weight: wrestler.weight_id,
|
||||
wrestler: {
|
||||
wrestler.id.to_s => { offical_weight: "108.2" }
|
||||
}
|
||||
}
|
||||
|
||||
assert_redirected_to "/tournaments/#{@tournament.id}/weigh_in/#{wrestler.weight_id}"
|
||||
assert_equal "Weights were successfully recorded.", flash[:notice]
|
||||
assert_equal 108.2, wrestler.reload.offical_weight.to_f
|
||||
|
||||
get :weigh_in_weight, params: { id: @tournament.id, weight: wrestler.weight_id }
|
||||
assert_response :success
|
||||
assert_equal 1, response.body.scan("Weights were successfully recorded.").size
|
||||
end
|
||||
|
||||
test "logged in non tournament owner cannot access post weigh_in_weight" do
|
||||
sign_in_non_owner
|
||||
post :weigh_in_weight, params: { id: 1, weight: 1, wrestler: @wrestlers }
|
||||
@@ -515,6 +559,36 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
get_up_matches
|
||||
success
|
||||
end
|
||||
|
||||
test "up matches uses turbo stream updates instead of timer refresh script" do
|
||||
@tournament.is_public = true
|
||||
@tournament.save
|
||||
get_up_matches
|
||||
success
|
||||
assert_includes response.body, "turbo-cable-stream-source"
|
||||
assert_includes response.body, "data-controller=\"up-matches-connection\""
|
||||
assert_includes response.body, "up-matches-cable-status-indicator"
|
||||
assert_not_includes response.body, "This page reloads every 30s"
|
||||
end
|
||||
|
||||
test "up matches shows full screen button when print param is not true" do
|
||||
@tournament.is_public = true
|
||||
@tournament.save
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
|
||||
assert_includes response.body, "Show Bout Board in Full Screen"
|
||||
assert_includes response.body, "print=true"
|
||||
end
|
||||
|
||||
test "up matches hides full screen button when print param is true" do
|
||||
@tournament.is_public = true
|
||||
@tournament.save
|
||||
get :up_matches, params: { id: @tournament.id, print: "true" }
|
||||
assert_response :success
|
||||
|
||||
assert_not_includes response.body, "Show Bout Board in Full Screen"
|
||||
end
|
||||
# END UP MATCHES PAGE PERMISSIONS
|
||||
|
||||
# ALL_RESULTS PAGE PERMISSIONS WHEN TOURNAMENT IS NOT PUBLIC
|
||||
@@ -599,11 +673,11 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
# END ALL_RESULTS PAGE PERMISSIONS
|
||||
|
||||
#TESTS THAT NEED MATCHES PUT ABOVE THIS
|
||||
test "redirect up_matches if no matches" do
|
||||
test "up_matches renders when no matches exist" do
|
||||
sign_in_owner
|
||||
wipe
|
||||
get :up_matches, params: { id: 1 }
|
||||
no_matches
|
||||
success
|
||||
end
|
||||
|
||||
test "redirect bracket if no matches" do
|
||||
@@ -686,6 +760,33 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
success
|
||||
end
|
||||
|
||||
test "delegate page renders created tournament delegate in html" do
|
||||
user = User.create!(
|
||||
email: "tournament_delegate_render_#{SecureRandom.hex(4)}@example.com",
|
||||
password: "password"
|
||||
)
|
||||
TournamentDelegate.create!(tournament_id: @tournament.id, user_id: user.id)
|
||||
|
||||
sign_in_owner
|
||||
get :delegate, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
assert_includes response.body, user.email
|
||||
end
|
||||
|
||||
test "school_delegate page renders created school delegate in html" do
|
||||
user = User.create!(
|
||||
email: "school_delegate_render_#{SecureRandom.hex(4)}@example.com",
|
||||
password: "password"
|
||||
)
|
||||
SchoolDelegate.create!(school_id: @school.id, user_id: user.id)
|
||||
|
||||
sign_in_owner
|
||||
get :school_delegate, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
assert_includes response.body, user.email
|
||||
assert_includes response.body, @school.name
|
||||
end
|
||||
|
||||
test 'logged in tournament owner can delete a school delegate' do
|
||||
sign_in_owner
|
||||
patch :remove_school_delegate, params: { id: 1, delegate: SchoolDelegate.find(1) }
|
||||
@@ -722,6 +823,16 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
success
|
||||
end
|
||||
|
||||
test "teampointadjust page lists created point deduction once in html" do
|
||||
sign_in_owner
|
||||
school = School.create!(name: "Point Deduction School #{SecureRandom.hex(3)}", tournament_id: @tournament.id)
|
||||
adjustment = Teampointadjust.create!(school_id: school.id, points: 9876.5)
|
||||
|
||||
get :teampointadjust, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
assert_equal 1, response.body.scan(adjustment.points.to_s).size
|
||||
end
|
||||
|
||||
test 'logged in tournament delegate cannot adjust team points' do
|
||||
sign_in_school_delegate
|
||||
get :teampointadjust, params: { id: 1 }
|
||||
@@ -758,6 +869,83 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
success
|
||||
end
|
||||
|
||||
test "matches page search finds by wrestler name, school name, and bout number" do
|
||||
sign_in_owner
|
||||
|
||||
search_school = School.create!(name: "Search Prep Academy", tournament_id: @tournament.id)
|
||||
search_wrestler = Wrestler.create!(
|
||||
name: "Alpha Searchman",
|
||||
school_id: search_school.id,
|
||||
weight_id: @tournament.weights.first.id,
|
||||
original_seed: 99,
|
||||
bracket_line: 99,
|
||||
season_loss: 0,
|
||||
season_win: 0,
|
||||
pool: 1
|
||||
)
|
||||
match = Match.create!(
|
||||
tournament_id: @tournament.id,
|
||||
weight_id: @tournament.weights.first.id,
|
||||
bout_number: 888999,
|
||||
w1: search_wrestler.id,
|
||||
w2: @wrestlers.first.id,
|
||||
bracket_position: "Pool",
|
||||
round: 1
|
||||
)
|
||||
|
||||
get :matches, params: { id: @tournament.id, search: "Searchman" }
|
||||
assert_response :success
|
||||
assert_includes response.body, match.bout_number.to_s
|
||||
|
||||
get :matches, params: { id: @tournament.id, search: "Search Prep" }
|
||||
assert_response :success
|
||||
assert_includes response.body, match.bout_number.to_s
|
||||
|
||||
get :matches, params: { id: @tournament.id, search: "888999" }
|
||||
assert_response :success
|
||||
assert_includes response.body, match.bout_number.to_s
|
||||
end
|
||||
|
||||
test "matches page paginates filtered results" do
|
||||
sign_in_owner
|
||||
|
||||
paging_school = School.create!(name: "Pager Academy", tournament_id: @tournament.id)
|
||||
paging_wrestler = Wrestler.create!(
|
||||
name: "Pager Wrestler",
|
||||
school_id: paging_school.id,
|
||||
weight_id: @tournament.weights.first.id,
|
||||
original_seed: 100,
|
||||
bracket_line: 100,
|
||||
season_loss: 0,
|
||||
season_win: 0,
|
||||
pool: 1
|
||||
)
|
||||
|
||||
55.times do |i|
|
||||
Match.create!(
|
||||
tournament_id: @tournament.id,
|
||||
weight_id: @tournament.weights.first.id,
|
||||
bout_number: 910000 + i,
|
||||
w1: paging_wrestler.id,
|
||||
w2: @wrestlers.first.id,
|
||||
bracket_position: "Pool",
|
||||
round: 1
|
||||
)
|
||||
end
|
||||
|
||||
get :matches, params: { id: @tournament.id, search: "Pager Academy" }
|
||||
assert_response :success
|
||||
assert_includes response.body, "Showing 1 - 50 of 55 matches"
|
||||
assert_includes response.body, "910000"
|
||||
assert_not_includes response.body, "910054"
|
||||
|
||||
get :matches, params: { id: @tournament.id, search: "Pager Academy", page: 2 }
|
||||
assert_response :success
|
||||
assert_includes response.body, "Showing 51 - 55 of 55 matches"
|
||||
assert_includes response.body, "910054"
|
||||
assert_not_includes response.body, "910000"
|
||||
end
|
||||
|
||||
test "logged in tournament owner can calculate team scores" do
|
||||
sign_in_owner
|
||||
post :calculate_team_scores, params: { id: 1 }
|
||||
@@ -950,6 +1138,25 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
assert_equal "School permission keys generated successfully.", flash[:notice]
|
||||
end
|
||||
|
||||
test "generated school permission keys are displayed on school delegate page" do
|
||||
sign_in_owner
|
||||
post :generate_school_keys, params: { id: @tournament.id }
|
||||
assert_redirected_to school_delegate_path(@tournament)
|
||||
|
||||
@tournament.schools.reload.each do |school|
|
||||
assert_not_nil school.permission_key, "Expected permission key for school #{school.id}"
|
||||
assert_not_empty school.permission_key, "Expected non-empty permission key for school #{school.id}"
|
||||
end
|
||||
|
||||
get :school_delegate, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
|
||||
@tournament.schools.each do |school|
|
||||
expected_link_fragment = "/schools/#{school.id}?school_permission_key=#{school.permission_key}"
|
||||
assert_includes response.body, expected_link_fragment
|
||||
end
|
||||
end
|
||||
|
||||
test "tournament delegate can delete school keys" do
|
||||
sign_in_delegate
|
||||
post :delete_school_keys, params: { id: @tournament.id }
|
||||
@@ -1180,4 +1387,52 @@ class TournamentsControllerTest < ActionController::TestCase
|
||||
expected_page2_display = [expected_page2_size, 20].min
|
||||
assert_equal expected_page2_display, assigns(:tournaments).size, "second page should contain the remaining tournaments (or up to per_page)"
|
||||
end
|
||||
|
||||
test "bout_sheets renders wrestler names, school names, and round for selected round" do
|
||||
tournament = create_double_elim_tournament_single_weight(8, "Regular Double Elimination 1-6")
|
||||
tournament.update!(user_id: users(:one).id, is_public: true)
|
||||
sign_in_owner
|
||||
|
||||
match = tournament.matches.where.not(w1: nil, w2: nil)
|
||||
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
|
||||
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
|
||||
.order(:bout_number)
|
||||
.first
|
||||
assert_not_nil match, "Expected at least one fully populated non-BYE match"
|
||||
|
||||
round = match.round.to_s
|
||||
w1 = Wrestler.find(match.w1)
|
||||
w2 = Wrestler.find(match.w2)
|
||||
|
||||
get :bout_sheets, params: { id: tournament.id, round: round }
|
||||
assert_response :success
|
||||
|
||||
assert_includes response.body, "Bout Number:</strong> #{match.bout_number}"
|
||||
assert_includes response.body, "Round:</strong> #{match.round}"
|
||||
assert_includes response.body, "#{w1.name}-#{w1.school.name}"
|
||||
assert_includes response.body, "#{w2.name}-#{w2.school.name}"
|
||||
end
|
||||
|
||||
test "bout_sheets filters out matches with BYE loser names" do
|
||||
tournament = create_double_elim_tournament_single_weight(8, "Regular Double Elimination 1-6")
|
||||
tournament.update!(user_id: users(:one).id, is_public: true)
|
||||
sign_in_owner
|
||||
|
||||
bye_match = tournament.matches.order(:bout_number).first
|
||||
assert_not_nil bye_match, "Expected at least one match to mark as BYE"
|
||||
bye_match.update!(loser1_name: "BYE")
|
||||
|
||||
non_bye_match = tournament.matches.where.not(id: bye_match.id).where.not(w1: nil, w2: nil)
|
||||
.where("loser1_name != ? OR loser1_name IS NULL", "BYE")
|
||||
.where("loser2_name != ? OR loser2_name IS NULL", "BYE")
|
||||
.order(:bout_number)
|
||||
.first
|
||||
assert_not_nil non_bye_match, "Expected at least one non-BYE match to remain"
|
||||
|
||||
get :bout_sheets, params: { id: tournament.id, round: "All" }
|
||||
assert_response :success
|
||||
|
||||
assert_not_includes response.body, "Bout Number:</strong> #{bye_match.bout_number}"
|
||||
assert_includes response.body, "Bout Number:</strong> #{non_bye_match.bout_number}"
|
||||
end
|
||||
end
|
||||
|
||||
180
test/controllers/up_matches_cache_test.rb
Normal file
180
test/controllers/up_matches_cache_test.rb
Normal file
@@ -0,0 +1,180 @@
|
||||
require "test_helper"
|
||||
|
||||
class UpMatchesCacheTest < ActionController::TestCase
|
||||
tests TournamentsController
|
||||
|
||||
setup do
|
||||
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 4, 2)
|
||||
@tournament.update!(user_id: users(:one).id)
|
||||
@tournament.reset_and_fill_bout_board
|
||||
|
||||
sign_in users(:one)
|
||||
|
||||
@original_perform_caching = ActionController::Base.perform_caching
|
||||
ActionController::Base.perform_caching = true
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
teardown do
|
||||
Rails.cache.clear
|
||||
ActionController::Base.perform_caching = @original_perform_caching
|
||||
end
|
||||
|
||||
test "up_matches row fragments hit cache and invalidate when a mat queue changes" do
|
||||
first_events = cache_events_for_up_matches do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial render to write row fragments"
|
||||
|
||||
second_events = cache_events_for_up_matches do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
assert_equal 0, cache_writes(second_events), "Expected second render to reuse cached row fragments"
|
||||
assert_operator cache_hits(second_events), :>, 0, "Expected second render to have cache hits"
|
||||
|
||||
mat = @tournament.mats.first
|
||||
mat.reload
|
||||
movable_match = mat.queue2_match || mat.queue1_match
|
||||
assert movable_match, "Expected at least one queued match to move"
|
||||
|
||||
third_events = cache_events_for_up_matches do
|
||||
mat.assign_match_to_queue!(movable_match, 4)
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
assert_operator cache_writes(third_events), :>, 0, "Expected queue change to invalidate and rewrite at least one row fragment"
|
||||
end
|
||||
|
||||
test "up_matches row fragments hit cache after queue clear rewrite" do
|
||||
first_events = cache_events_for_up_matches do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial render to write row fragments"
|
||||
|
||||
mat = @tournament.mats.first
|
||||
clear_events = cache_events_for_up_matches do
|
||||
mat.clear_queue!
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(clear_events), :>, 0, "Expected queue clear to invalidate and rewrite at least one row fragment"
|
||||
|
||||
repeat_events = cache_events_for_up_matches do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(repeat_events), "Expected subsequent render after queue clear rewrite to reuse cached row fragments"
|
||||
assert_operator cache_hits(repeat_events), :>, 0, "Expected cache hits after queue clear rewrite"
|
||||
end
|
||||
|
||||
test "up_matches unassigned row fragments hit cache and invalidate after unassigned match update" do
|
||||
key_markers = %w[up_matches_unassigned_row]
|
||||
|
||||
first_events = cache_events_for_up_matches(key_markers) do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial unassigned row render to write fragments"
|
||||
|
||||
second_events = cache_events_for_up_matches(key_markers) do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(second_events), "Expected repeat unassigned row render to reuse cached fragments"
|
||||
assert_operator cache_hits(second_events), :>, 0, "Expected repeat unassigned row render to hit cache"
|
||||
|
||||
unassigned_match = @tournament.up_matches_unassigned_matches.first
|
||||
assert unassigned_match, "Expected at least one unassigned match for cache invalidation test"
|
||||
|
||||
third_events = cache_events_for_up_matches(key_markers) do
|
||||
unassigned_match.touch
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(third_events), :>, 0, "Expected unassigned match update to invalidate unassigned row fragment"
|
||||
end
|
||||
|
||||
test "completing an on-mat match expires up_matches cached fragments" do
|
||||
warm_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(warm_events), :>, 0, "Expected initial up_matches render to warm caches"
|
||||
|
||||
mat = @tournament.mats.detect { |m| m.queue1_match.present? }
|
||||
assert mat, "Expected a mat with a queued match"
|
||||
match = mat.queue1_match
|
||||
assert match, "Expected queue1 match to complete"
|
||||
|
||||
post_action_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||
match.update!(
|
||||
finished: 1,
|
||||
winner_id: match.w1 || match.w2,
|
||||
win_type: "Decision",
|
||||
score: "1-0"
|
||||
)
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
assert_operator cache_writes(post_action_events), :>, 0, "Expected completed match to expire and rewrite up_matches caches"
|
||||
end
|
||||
|
||||
test "manually assigning an unassigned match to a mat queue expires up_matches caches" do
|
||||
warm_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(warm_events), :>, 0, "Expected initial up_matches render to warm caches"
|
||||
|
||||
unassigned_match = @tournament.up_matches_unassigned_matches.first
|
||||
assert unassigned_match, "Expected at least one unassigned match to manually place on a mat"
|
||||
target_mat = @tournament.mats.first
|
||||
|
||||
post_action_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
|
||||
target_mat.assign_match_to_queue!(unassigned_match, 4)
|
||||
get :up_matches, params: { id: @tournament.id }
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
assert_operator cache_writes(post_action_events), :>, 0, "Expected manual mat assignment to expire and rewrite up_matches caches"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_events_for_up_matches(key_markers = %w[up_matches_mat_row up_matches_unassigned_row])
|
||||
events = []
|
||||
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||
key = payload[:key].to_s
|
||||
next unless key_markers.any? { |marker| key.include?(marker) }
|
||||
|
||||
events << { name: name, hit: payload[:hit] }
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribed(
|
||||
subscriber,
|
||||
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||
) do
|
||||
yield
|
||||
end
|
||||
|
||||
events
|
||||
end
|
||||
|
||||
def cache_writes(events)
|
||||
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||
end
|
||||
|
||||
def cache_hits(events)
|
||||
events.count do |event|
|
||||
event[:name] == "cache_fetch_hit.active_support" ||
|
||||
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||
end
|
||||
end
|
||||
end
|
||||
107
test/controllers/weight_show_cache_test.rb
Normal file
107
test/controllers/weight_show_cache_test.rb
Normal file
@@ -0,0 +1,107 @@
|
||||
require "test_helper"
|
||||
|
||||
class WeightShowCacheTest < ActionController::TestCase
|
||||
tests WeightsController
|
||||
|
||||
setup do
|
||||
create_a_tournament_with_single_weight("Regular Double Elimination 1-6", 8)
|
||||
@tournament.update!(user_id: users(:one).id)
|
||||
@weight = @tournament.weights.first
|
||||
|
||||
@original_perform_caching = ActionController::Base.perform_caching
|
||||
ActionController::Base.perform_caching = true
|
||||
Rails.cache.clear
|
||||
end
|
||||
|
||||
teardown do
|
||||
Rails.cache.clear
|
||||
ActionController::Base.perform_caching = @original_perform_caching
|
||||
end
|
||||
|
||||
test "weight show readonly row fragments hit cache and invalidate after wrestler update" do
|
||||
first_events = cache_events_for_weight_show do
|
||||
get :show, params: { id: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(first_events), :>, 0, "Expected initial weight show render to write readonly row fragments"
|
||||
|
||||
second_events = cache_events_for_weight_show do
|
||||
get :show, params: { id: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_equal 0, cache_writes(second_events), "Expected repeat weight show render to reuse readonly row fragments"
|
||||
assert_operator cache_hits(second_events), :>, 0, "Expected repeat weight show render to hit readonly row cache"
|
||||
|
||||
wrestler = @weight.wrestlers.first
|
||||
third_events = cache_events_for_weight_show do
|
||||
wrestler.touch
|
||||
get :show, params: { id: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_writes(third_events), :>, 0, "Expected wrestler update to invalidate weight show readonly row cache"
|
||||
end
|
||||
|
||||
test "weight show does not leak manage-only controls from cache across users" do
|
||||
sign_in users(:one)
|
||||
get :show, params: { id: @weight.id }
|
||||
assert_response :success
|
||||
assert_includes response.body, "Save Seeds"
|
||||
assert_match(/fa-trash-alt/, response.body)
|
||||
assert_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
|
||||
|
||||
sign_out
|
||||
|
||||
get :show, params: { id: @weight.id }
|
||||
assert_response :success
|
||||
assert_not_includes response.body, "Save Seeds"
|
||||
assert_no_match(/fa-trash-alt/, response.body)
|
||||
assert_no_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
|
||||
|
||||
spectator_cache_events = cache_events_for_weight_show do
|
||||
get :show, params: { id: @weight.id }
|
||||
assert_response :success
|
||||
end
|
||||
assert_operator cache_hits(spectator_cache_events), :>, 0, "Expected repeat spectator request to hit readonly wrestler row cache"
|
||||
assert_not_includes response.body, "Save Seeds"
|
||||
assert_no_match(/fa-trash-alt/, response.body)
|
||||
assert_no_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sign_out
|
||||
@request.session[:user_id] = nil
|
||||
@controller.instance_variable_set(:@current_user, nil)
|
||||
@controller.instance_variable_set(:@current_ability, nil)
|
||||
end
|
||||
|
||||
def cache_events_for_weight_show
|
||||
events = []
|
||||
subscriber = lambda do |name, _start, _finish, _id, payload|
|
||||
key = payload[:key].to_s
|
||||
next unless key.include?("weight_show_wrestler_row")
|
||||
|
||||
events << { name: name, hit: payload[:hit] }
|
||||
end
|
||||
|
||||
ActiveSupport::Notifications.subscribed(
|
||||
subscriber,
|
||||
/cache_(read|write|fetch_hit|generate)\.active_support/
|
||||
) do
|
||||
yield
|
||||
end
|
||||
|
||||
events
|
||||
end
|
||||
|
||||
def cache_writes(events)
|
||||
events.count { |event| event[:name] == "cache_write.active_support" }
|
||||
end
|
||||
|
||||
def cache_hits(events)
|
||||
events.count do |event|
|
||||
event[:name] == "cache_fetch_hit.active_support" ||
|
||||
(event[:name] == "cache_read.active_support" && event[:hit])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,15 @@ class DoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
return wrestler
|
||||
end
|
||||
|
||||
def wrestle_other_match_in_round(reference_match, conso: false)
|
||||
match = @tournament.matches.reload
|
||||
.select { |m| m.round == reference_match.round && m.id != reference_match.id && m.is_consolation_match == conso }
|
||||
.first
|
||||
return if match.nil?
|
||||
|
||||
winner_by_name("Test2", match)
|
||||
end
|
||||
|
||||
test "Wrestlers get points for byes in the championship rounds" do
|
||||
matches = @tournament.matches.reload
|
||||
round1 = matches.select{|m| m.bracket_position == "Bracket Round of 16"}.first
|
||||
@@ -37,9 +46,10 @@ class DoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
semi = matches.select{|m| m.bracket_position == "Semis"}.first
|
||||
winner_by_name_by_bye("Test1", round1)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
wrestle_other_match_in_round(round1, conso: false)
|
||||
winner_by_name("Test1", semi)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 4
|
||||
assert wrestler_points_calc.byePoints == 2
|
||||
end
|
||||
|
||||
test "Wrestlers get points for byes in the consolation rounds" do
|
||||
@@ -49,9 +59,10 @@ class DoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
semi = matches.select{|m| m.bracket_position == "Conso Semis"}.first
|
||||
winner_by_name_by_bye("Test1", conso_r8_1)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
wrestle_other_match_in_round(conso_r8_1, conso: true)
|
||||
winner_by_name("Test1", semi)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 2
|
||||
assert wrestler_points_calc.byePoints == 1
|
||||
end
|
||||
|
||||
test "Wrestlers do not get bye points if they get byes to 1st/2nd and win by bye" do
|
||||
@@ -93,7 +104,19 @@ class DoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
winner_by_name_by_bye("Test1", semi)
|
||||
winner_by_name("Test1", final)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 6
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
|
||||
test "Wrestlers do not get championship bye points when no championship match is wrestled in those bye rounds" do
|
||||
matches = @tournament.matches.reload
|
||||
round1 = matches.select{|m| m.bracket_position == "Bracket Round of 16"}.first
|
||||
quarter = matches.select{|m| m.bracket_position == "Quarter"}.first
|
||||
semi = matches.select{|m| m.bracket_position == "Semis"}.first
|
||||
winner_by_name_by_bye("Test1", round1)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
winner_by_name("Test1", semi)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
|
||||
test "Wrestlers do not get bye points if they get byes to 3rd/4th and win by decision" do
|
||||
@@ -107,6 +130,20 @@ class DoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
winner_by_name_by_bye("Test1", semi)
|
||||
winner_by_name("Test1", final)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 3
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
|
||||
test "Wrestlers do not get conso bye points when no conso match is wrestled in those rounds" do
|
||||
matches = @tournament.matches.reload
|
||||
conso_r8_1 = matches.select{|m| m.bracket_position == "Conso Round of 8.1"}.first
|
||||
quarter = matches.select{|m| m.bracket_position == "Conso Quarter"}.first
|
||||
semi = matches.select{|m| m.bracket_position == "Conso Semis"}.first
|
||||
final = matches.select{|m| m.bracket_position == "3/4"}.first
|
||||
winner_by_name_by_bye("Test1", conso_r8_1)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
winner_by_name_by_bye("Test1", semi)
|
||||
winner_by_name("Test1", final)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
end
|
||||
37
test/integration/mat_stats_live_updates_test.rb
Normal file
37
test/integration/mat_stats_live_updates_test.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
require "test_helper"
|
||||
|
||||
class MatStatsLiveUpdatesTest < ActionDispatch::IntegrationTest
|
||||
include ActionView::RecordIdentifier
|
||||
|
||||
test "destroying all matches broadcasts no-match state for mat stats stream" do
|
||||
create_double_elim_tournament_single_weight_1_6(4)
|
||||
mat = @tournament.mats.create!(name: "Mat 1")
|
||||
@tournament.reset_and_fill_bout_board
|
||||
stream = stream_name_for(mat)
|
||||
|
||||
clear_streams(stream)
|
||||
@tournament.destroy_all_matches
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
payload = broadcasts_for(stream).last
|
||||
assert_includes payload, dom_id(mat, :current_match)
|
||||
assert_includes payload, "No matches assigned to this mat."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcasts_for(stream)
|
||||
ActionCable.server.pubsub.broadcasts(stream)
|
||||
end
|
||||
|
||||
def clear_streams(*streams)
|
||||
ActionCable.server.pubsub.clear
|
||||
streams.each do |stream|
|
||||
broadcasts_for(stream).clear
|
||||
end
|
||||
end
|
||||
|
||||
def stream_name_for(streamable)
|
||||
Turbo::StreamsChannel.send(:stream_name_from, [streamable])
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,15 @@ class ModifiedDoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
return wrestler
|
||||
end
|
||||
|
||||
def wrestle_other_match_in_round(reference_match, conso: false)
|
||||
match = @tournament.matches.reload
|
||||
.select { |m| m.round == reference_match.round && m.id != reference_match.id && m.is_consolation_match == conso }
|
||||
.first
|
||||
return if match.nil?
|
||||
|
||||
winner_by_name("Test2", match)
|
||||
end
|
||||
|
||||
test "Wrestlers get points for byes in the championship rounds" do
|
||||
matches = @tournament.matches.reload
|
||||
round1 = matches.select{|m| m.round == 1}.first
|
||||
@@ -37,9 +46,10 @@ class ModifiedDoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
semi = matches.select{|m| m.bracket_position == "Semis"}.first
|
||||
winner_by_name_by_bye("Test1", round1)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
wrestle_other_match_in_round(round1, conso: false)
|
||||
winner_by_name("Test1", semi)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 4
|
||||
assert wrestler_points_calc.byePoints == 2
|
||||
end
|
||||
|
||||
test "Wrestlers get points for byes in the consolation rounds" do
|
||||
@@ -49,9 +59,10 @@ class ModifiedDoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
semi = matches.select{|m| m.bracket_position == "Conso Semis"}.first
|
||||
winner_by_name_by_bye("Test1", round2)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
wrestle_other_match_in_round(round2, conso: true)
|
||||
winner_by_name("Test1", semi)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 2
|
||||
assert wrestler_points_calc.byePoints == 1
|
||||
end
|
||||
|
||||
test "Wrestlers do not get bye points if they get byes to 1st/2nd and win by bye" do
|
||||
@@ -93,7 +104,19 @@ class ModifiedDoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
winner_by_name_by_bye("Test1", semi)
|
||||
winner_by_name("Test1", final)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 6
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
|
||||
test "Wrestlers do not get championship bye points when no championship match is wrestled in those bye rounds" do
|
||||
matches = @tournament.matches.reload
|
||||
round1 = matches.select{|m| m.round == 1}.first
|
||||
quarter = matches.select{|m| m.bracket_position == "Quarter"}.first
|
||||
semi = matches.select{|m| m.bracket_position == "Semis"}.first
|
||||
winner_by_name_by_bye("Test1", round1)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
winner_by_name("Test1", semi)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
|
||||
test "Wrestlers do not get bye points if they get byes to 5th/6th and win by decision" do
|
||||
@@ -107,6 +130,20 @@ class ModifiedDoubleEliminationWrestlerScore < ActionDispatch::IntegrationTest
|
||||
winner_by_name_by_bye("Test1", semi)
|
||||
winner_by_name("Test1", final)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 3
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
|
||||
test "Wrestlers do not get conso bye points when no conso match is wrestled in those rounds" do
|
||||
matches = @tournament.matches.reload
|
||||
round2 = matches.select{|m| m.bracket_position == "Conso Round of 8"}.first
|
||||
quarter = matches.select{|m| m.bracket_position == "Conso Quarter"}.first
|
||||
semi = matches.select{|m| m.bracket_position == "Conso Semis"}.first
|
||||
final = matches.select{|m| m.bracket_position == "5/6"}.first
|
||||
winner_by_name_by_bye("Test1", round2)
|
||||
winner_by_name_by_bye("Test1", quarter)
|
||||
winner_by_name_by_bye("Test1", semi)
|
||||
winner_by_name("Test1", final)
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(get_wretler_by_name("Test1"))
|
||||
assert wrestler_points_calc.byePoints == 0
|
||||
end
|
||||
end
|
||||
46
test/integration/pool_bye_points_rules_test.rb
Normal file
46
test/integration/pool_bye_points_rules_test.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
require 'test_helper'
|
||||
|
||||
class PoolByePointsRulesTest < ActionDispatch::IntegrationTest
|
||||
def finish_pool_match_for_wrestler(wrestler)
|
||||
match = wrestler.pool_matches.select { |m| m.finished != 1 }.first
|
||||
return if match.nil?
|
||||
|
||||
match.w1 = wrestler.id
|
||||
match.winner_id = wrestler.id
|
||||
match.finished = 1
|
||||
match.win_type = "Decision"
|
||||
match.score = "1-0"
|
||||
match.save!
|
||||
end
|
||||
|
||||
test "single pool wrestlers do not get pool bye points" do
|
||||
create_pool_tournament_single_weight(6)
|
||||
wrestler = @tournament.weights.first.wrestlers.first
|
||||
finish_pool_match_for_wrestler(wrestler)
|
||||
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(wrestler)
|
||||
assert_equal 0, wrestler_points_calc.byePoints
|
||||
end
|
||||
|
||||
test "pool bye points are not awarded when pools are even" do
|
||||
create_pool_tournament_single_weight(8)
|
||||
wrestler = @tournament.weights.first.wrestlers.first
|
||||
finish_pool_match_for_wrestler(wrestler)
|
||||
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(wrestler)
|
||||
assert_equal 0, wrestler_points_calc.byePoints
|
||||
end
|
||||
|
||||
test "pool bye points are awarded once when wrestler is in a smaller pool" do
|
||||
create_pool_tournament_single_weight(9)
|
||||
weight = @tournament.weights.first
|
||||
smallest_pool = (1..weight.pools).min_by { |pool_number| weight.wrestlers_in_pool(pool_number).size }
|
||||
wrestler = weight.wrestlers_in_pool(smallest_pool).first
|
||||
|
||||
finish_pool_match_for_wrestler(wrestler)
|
||||
finish_pool_match_for_wrestler(wrestler)
|
||||
|
||||
wrestler_points_calc = CalculateWrestlerTeamScore.new(wrestler)
|
||||
assert_equal 2, wrestler_points_calc.byePoints
|
||||
end
|
||||
end
|
||||
@@ -25,6 +25,7 @@ class RandomSeedingTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
test "There are the same number of matches in the top half and bottom half of a double elimination tournament in round 1" do
|
||||
# This has to be an even number otherwise there will obviously be a discrepency
|
||||
create_double_elim_tournament_single_weight(18, "Regular Double Elimination 1-8")
|
||||
clean_up_original_seeds(@tournament)
|
||||
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
|
||||
@@ -35,6 +36,7 @@ class RandomSeedingTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
|
||||
test "There are the same number of matches in the top half and bottom half of a double elimination tournament in round 1 in a 6 man bracket" do
|
||||
# This has to be an even number otherwise there will obviously be a discrepency
|
||||
create_double_elim_tournament_single_weight(6, "Regular Double Elimination 1-8")
|
||||
clean_up_original_seeds(@tournament)
|
||||
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
|
||||
@@ -52,4 +54,22 @@ class RandomSeedingTest < ActionDispatch::IntegrationTest
|
||||
assert round_one_matches.select{|m| m.w1.nil? and m.w2.nil? }.size == 0
|
||||
assert conso_round_one_matches.select{|m| m.loser1_name == "BYE" and m.loser2_name == "BYE" }.size == 0
|
||||
end
|
||||
|
||||
test "There are no double byes in a 64 person double elimination tournament in round 1" do
|
||||
create_double_elim_tournament_single_weight(33, "Regular Double Elimination 1-8")
|
||||
clean_up_original_seeds(@tournament)
|
||||
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
|
||||
assert round_one_matches.select{|m| m.w1.nil? and m.w2.nil? }.size == 0
|
||||
end
|
||||
|
||||
test "There are the same number of matches in the top half and bottom half of a 64 person double elimination tournament in round 1" do
|
||||
# This has to be an even number otherwise there will obviously be a discrepency
|
||||
create_double_elim_tournament_single_weight(34, "Regular Double Elimination 1-8")
|
||||
clean_up_original_seeds(@tournament)
|
||||
round_one_matches = @tournament.matches.reload.select{|m| m.round == 1}
|
||||
# 64 man bracket there are 32 matches so top half is bracket_position_number 1-16 and bottom is 17-32
|
||||
round_one_top_half = round_one_matches.select{|m| !m.w1.nil? and !m.w2.nil? and m.bracket_position_number < 17}
|
||||
round_one_bottom_half = round_one_matches.select{|m| !m.w1.nil? and !m.w2.nil? and m.bracket_position_number > 16}
|
||||
assert round_one_top_half.size == round_one_bottom_half.size
|
||||
end
|
||||
end
|
||||
@@ -11,4 +11,19 @@ class MatTest < ActiveSupport::TestCase
|
||||
assert_equal [:tournament, :name], mat.errors.attribute_names
|
||||
end
|
||||
|
||||
test "queue_matches refreshes after queue slots change and record reloads" do
|
||||
create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(16, 4, 1)
|
||||
@tournament.reset_and_fill_bout_board
|
||||
|
||||
mat = @tournament.mats.first
|
||||
initial_queue_ids = mat.queue_matches.map { |match| match&.id }
|
||||
assert initial_queue_ids.compact.any?, "Expected initial queue to contain matches"
|
||||
|
||||
Mat.where(id: mat.id).update_all(queue1: nil, queue2: nil, queue3: nil, queue4: nil)
|
||||
mat.reload
|
||||
|
||||
refreshed_queue_ids = mat.queue_matches.map { |match| match&.id }
|
||||
assert_equal [nil, nil, nil, nil], refreshed_queue_ids, "Expected queue_matches to refresh after reload and slot changes"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -49,6 +49,38 @@ class MatchBroadcastTest < ActiveSupport::TestCase
|
||||
assert_includes broadcasts_for(stream).last, dom_id(mat, :current_match)
|
||||
end
|
||||
|
||||
test "destroy_all_matches clears mats and broadcasts no matches assigned" do
|
||||
create_double_elim_tournament_single_weight_1_6(4)
|
||||
mat = @tournament.mats.create!(name: "Mat 1")
|
||||
@tournament.reset_and_fill_bout_board
|
||||
stream = stream_name_for(mat)
|
||||
|
||||
clear_streams(stream)
|
||||
@tournament.destroy_all_matches
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
payload = broadcasts_for(stream).last
|
||||
assert_includes payload, dom_id(mat, :current_match)
|
||||
assert_includes payload, "No matches assigned to this mat."
|
||||
assert_equal [nil, nil, nil, nil], mat.reload.queue_match_ids
|
||||
end
|
||||
|
||||
test "wipe tournament matches service clears mats and broadcasts no matches assigned" do
|
||||
create_double_elim_tournament_single_weight_1_6(4)
|
||||
mat = @tournament.mats.create!(name: "Mat 1")
|
||||
@tournament.reset_and_fill_bout_board
|
||||
stream = stream_name_for(mat)
|
||||
|
||||
clear_streams(stream)
|
||||
WipeTournamentMatches.new(@tournament).setUpMatchGeneration
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
payload = broadcasts_for(stream).last
|
||||
assert_includes payload, dom_id(mat, :current_match)
|
||||
assert_includes payload, "No matches assigned to this mat."
|
||||
assert_equal [nil, nil, nil, nil], mat.reload.queue_match_ids
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcasts_for(stream)
|
||||
|
||||
117
test/models/up_matches_broadcast_test.rb
Normal file
117
test/models/up_matches_broadcast_test.rb
Normal file
@@ -0,0 +1,117 @@
|
||||
require "test_helper"
|
||||
|
||||
class UpMatchesBroadcastTest < ActiveSupport::TestCase
|
||||
test "tournament broadcaster emits replace action for up matches board" do
|
||||
tournament = tournaments(:one)
|
||||
stream = stream_name_for(tournament)
|
||||
clear_streams(stream)
|
||||
|
||||
Tournament.broadcast_up_matches_board(tournament.id)
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
payload = broadcasts_for(stream).last
|
||||
assert_up_matches_replace_payload(payload)
|
||||
end
|
||||
|
||||
test "mat queue change broadcasts up matches board update" do
|
||||
tournament = tournaments(:one)
|
||||
mat = mats(:one)
|
||||
match = matches(:tournament_1_bout_2000)
|
||||
stream = stream_name_for(tournament)
|
||||
clear_streams(stream)
|
||||
|
||||
mat.update!(queue1: match.id)
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
assert_up_matches_replace_payload(broadcasts_for(stream).last)
|
||||
end
|
||||
|
||||
test "mat clear_queue broadcasts up matches board update" do
|
||||
tournament = tournaments(:one)
|
||||
mat = mats(:one)
|
||||
match = matches(:tournament_1_bout_2000)
|
||||
stream = stream_name_for(tournament)
|
||||
|
||||
mat.update!(queue1: match.id)
|
||||
clear_streams(stream)
|
||||
|
||||
mat.clear_queue!
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
assert_up_matches_replace_payload(broadcasts_for(stream).last)
|
||||
end
|
||||
|
||||
test "match mat assignment change broadcasts up matches board update" do
|
||||
tournament = tournaments(:one)
|
||||
mat = mats(:one)
|
||||
match = matches(:tournament_1_bout_2001)
|
||||
stream = stream_name_for(tournament)
|
||||
clear_streams(stream)
|
||||
|
||||
match.update!(mat_id: mat.id)
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
assert_up_matches_replace_payload(broadcasts_for(stream).last)
|
||||
end
|
||||
|
||||
test "match mat unassignment broadcasts up matches board update" do
|
||||
tournament = tournaments(:one)
|
||||
mat = mats(:one)
|
||||
match = matches(:tournament_1_bout_2001)
|
||||
stream = stream_name_for(tournament)
|
||||
|
||||
match.update!(mat_id: mat.id)
|
||||
clear_streams(stream)
|
||||
|
||||
match.update!(mat_id: nil)
|
||||
|
||||
assert_operator broadcasts_for(stream).size, :>, 0
|
||||
assert_up_matches_replace_payload(broadcasts_for(stream).last)
|
||||
end
|
||||
|
||||
test "mat update without queue slot changes does not broadcast up matches board update" do
|
||||
tournament = tournaments(:one)
|
||||
mat = mats(:one)
|
||||
stream = stream_name_for(tournament)
|
||||
clear_streams(stream)
|
||||
|
||||
mat.update!(name: "Mat One Renamed")
|
||||
|
||||
assert_equal 0, broadcasts_for(stream).size
|
||||
end
|
||||
|
||||
test "match update without mat_id change does not broadcast up matches board update" do
|
||||
tournament = tournaments(:one)
|
||||
match = matches(:tournament_1_bout_2001)
|
||||
stream = stream_name_for(tournament)
|
||||
clear_streams(stream)
|
||||
|
||||
match.update!(w1_stat: "Local stat change")
|
||||
|
||||
assert_equal 0, broadcasts_for(stream).size
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def broadcasts_for(stream)
|
||||
ActionCable.server.pubsub.broadcasts(stream)
|
||||
end
|
||||
|
||||
def clear_streams(*streams)
|
||||
ActionCable.server.pubsub.clear
|
||||
streams.each do |stream|
|
||||
broadcasts_for(stream).clear
|
||||
end
|
||||
end
|
||||
|
||||
def stream_name_for(streamable)
|
||||
Turbo::StreamsChannel.send(:stream_name_from, [streamable])
|
||||
end
|
||||
|
||||
# Broadcast payloads may be JSON-escaped in test adapters, so assert semantic markers.
|
||||
def assert_up_matches_replace_payload(payload)
|
||||
assert_includes payload, "up_matches_board"
|
||||
assert_includes payload, "replace"
|
||||
assert_includes payload, "turbo-stream"
|
||||
end
|
||||
end
|
||||
158
vendor/assets/javascripts/jquery.dataTables.min.js
vendored
158
vendor/assets/javascripts/jquery.dataTables.min.js
vendored
@@ -1,158 +0,0 @@
|
||||
/*! DataTables 1.10.6
|
||||
* ©2008-2015 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
(function(Ea,P,k){var O=function(h){function V(a){var b,c,e={};h.each(a,function(d){if((b=d.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=d.replace(b[0],b[2].toLowerCase()),e[c]=d,"o"===b[1]&&V(a[d])});a._hungarianMap=e}function H(a,b,c){a._hungarianMap||V(a);var e;h.each(b,function(d){e=a._hungarianMap[d];if(e!==k&&(c||b[e]===k))"o"===e.charAt(0)?(b[e]||(b[e]={}),h.extend(!0,b[e],b[d]),H(a[e],b[e],c)):b[e]=b[d]})}function O(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;
|
||||
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");
|
||||
A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&H(m.models.oSearch,a[b])}function fb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function gb(a){var a=a.oBrowser,b=h("<div/>").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",
|
||||
top:1,left:1,width:100,overflow:"scroll"}).append(h('<div class="test"/>').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==Math.round(c.offset().left);b.remove()}function hb(a,b,c,e,d,f){var g,i=!1;c!==k&&(g=c,i=!0);for(;e!==d;)a.hasOwnProperty(e)&&(g=i?b(g,a[e],e,a):a[e],i=!0,e+=f);return g}function Fa(a,b){var c=m.defaults.column,e=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:P.createElement("th"),sTitle:c.sTitle?
|
||||
c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[e],mData:c.mData?c.mData:e,idx:e});a.aoColumns.push(c);c=a.aoPreSearchCols;c[e]=h.extend({},m.models.oSearch,c[e]);ka(a,e,h(b).data())}function ka(a,b,c){var b=a.aoColumns[b],e=a.oClasses,d=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=d.attr("width")||null;var f=(d.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),H(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&
|
||||
(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,i=W(g),j=b.mRender?W(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var e=i(a,b,k,c);return j&&b?j(e,b,a,c):e};b.fnSetData=function(a,b,c){return Q(g)(a,b,c)};"number"!==typeof g&&
|
||||
(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,d.addClass(e.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=e.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=e.sSortableAsc,b.sSortingClassJUI=e.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=e.sSortableDesc,b.sSortingClassJUI=e.sSortJUIDescAllowed):(b.sSortingClass=e.sSortable,b.sSortingClassJUI=e.sSortJUI)}function X(a){if(!1!==a.oFeatures.bAutoWidth){var b=
|
||||
a.aoColumns;Ga(a);for(var c=0,e=b.length;c<e;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Y(a);w(a,null,"column-sizing",[a])}function la(a,b){var c=Z(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=Z(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function aa(a){return Z(a,"bVisible").length}function Z(a,b){var c=[];h.map(a.aoColumns,function(a,d){a[b]&&c.push(d)});return c}function Ha(a){var b=a.aoColumns,c=a.aoData,e=m.ext.type.detect,d,
|
||||
f,g,i,j,h,l,q,o;d=0;for(f=b.length;d<f;d++)if(l=b[d],o=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(i=e.length;g<i;g++){j=0;for(h=c.length;j<h;j++){o[j]===k&&(o[j]=y(a,j,d,"type"));q=e[g](o[j],a);if(!q&&g!==e.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function ib(a,b,c,e){var d,f,g,i,j,n,l=a.aoColumns;if(b)for(d=b.length-1;0<=d;d--){n=b[d];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<
|
||||
g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Fa(a);e(q[f],n)}else if("number"===typeof q[f]&&0>q[f])e(l.length+q[f],n);else if("string"===typeof q[f]){i=0;for(j=l.length;i<j;i++)("_all"==q[f]||h(l[i].nTh).hasClass(q[f]))&&e(i,n)}}if(c){d=0;for(a=c.length;d<a;d++)e(d,c[d])}}function J(a,b,c,e){var d=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data"});f._aData=b;a.aoData.push(f);for(var b=a.aoColumns,f=0,g=b.length;f<g;f++)c&&Ia(a,d,f,y(a,d,f)),b[f].sType=null;a.aiDisplayMaster.push(d);
|
||||
(c||!a.oFeatures.bDeferRender)&&Ja(a,d,c,e);return d}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,d){c=na(a,d);return J(a,c.data,d,c.cells)})}function y(a,b,c,e){var d=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,i=f.sDefaultContent,c=f.fnGetData(g,e,{settings:a,row:b,col:c});if(c===k)return a.iDrawError!=d&&null===i&&(R(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=d),i;if((c===g||null===c)&&
|
||||
null!==i)c=i;else if("function"===typeof c)return c.call(g);return null===c&&"display"==e?"":c}function Ia(a,b,c,e){a.aoColumns[c].fnSetData(a.aoData[b]._aData,e,{settings:a,row:b,col:c})}function Ka(a){return h.map(a.match(/(\\.|[^\.])+/g),function(a){return a.replace(/\\./g,".")})}function W(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=W(c))});return function(a,c,f,g){var i=b[c]||b._;return i!==k?i(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,
|
||||
c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,i;if(""!==f){i=Ka(f);for(var j=0,h=i.length;j<h;j++){f=i[j].match(ba);g=i[j].match(S);if(f){i[j]=i[j].replace(ba,"");""!==i[j]&&(a=a[i[j]]);g=[];i.splice(0,j+1);i=i.join(".");j=0;for(h=a.length;j<h;j++)g.push(c(a[j],b,i));a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){i[j]=i[j].replace(S,"");a=a[i[j]]();continue}if(null===a||a[i[j]]===
|
||||
k)return k;a=a[i[j]]}}return a};return function(b,d){return c(b,d,a)}}return function(b){return b[a]}}function Q(a){if(h.isPlainObject(a))return Q(a._);if(null===a)return function(){};if("function"===typeof a)return function(b,e,d){a(b,"set",e,d)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,e,d){var d=Ka(d),f;f=d[d.length-1];for(var g,i,j=0,h=d.length-1;j<h;j++){g=d[j].match(ba);i=d[j].match(S);if(g){d[j]=d[j].replace(ba,"");a[d[j]]=[];
|
||||
f=d.slice();f.splice(0,j+1);g=f.join(".");i=0;for(h=e.length;i<h;i++)f={},b(f,e[i],g),a[d[j]].push(f);return}i&&(d[j]=d[j].replace(S,""),a=a[d[j]](e));if(null===a[d[j]]||a[d[j]]===k)a[d[j]]={};a=a[d[j]]}if(f.match(S))a[f.replace(S,"")](e);else a[f.replace(ba,"")]=e};return function(c,e){return b(c,e,a)}}return function(b,e){b[a]=e}}function La(a){return D(a.aoData,"_aData")}function oa(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0}function pa(a,b,c){for(var e=-1,d=0,f=a.length;d<
|
||||
f;d++)a[d]==b?e=d:a[d]>b&&a[d]--; -1!=e&&c===k&&a.splice(e,1)}function ca(a,b,c,e){var d=a.aoData[b],f,g=function(c,f){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=y(a,b,f,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===d.src)d._aData=na(a,d,e,e===k?k:d._aData).data;else{var i=d.anCells;if(i)if(e!==k)g(i[e],e);else{c=0;for(f=i.length;c<f;c++)g(i[c],c)}}d._aSortData=null;d._aFilterData=null;g=a.aoColumns;if(e!==k)g[e].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;
|
||||
Ma(d)}}function na(a,b,c,e){var d=[],f=b.firstChild,g,i=0,j,n=a.aoColumns,l=a._rowReadObject,e=e||l?{}:[],q=function(a,b){if("string"===typeof a){var c=a.indexOf("@");-1!==c&&(c=a.substring(c+1),Q(a)(e,b.getAttribute(c)))}},a=function(a){if(c===k||c===i)g=n[i],j=h.trim(a.innerHTML),g&&g._bAttrSrc?(Q(g.mData._)(e,j),q(g.mData.sort,a),q(g.mData.type,a),q(g.mData.filter,a)):l?(g._setter||(g._setter=Q(g.mData)),g._setter(e,j)):e[i]=j;i++};if(f)for(;f;){b=f.nodeName.toUpperCase();if("TD"==b||"TH"==b)a(f),
|
||||
d.push(f);f=f.nextSibling}else{d=b.anCells;f=0;for(b=d.length;f<b;f++)a(d[f])}return{data:e,cells:d}}function Ja(a,b,c,e){var d=a.aoData[b],f=d._aData,g=[],i,j,h,l,q;if(null===d.nTr){i=c||P.createElement("tr");d.nTr=i;d.anCells=g;i._DT_RowIndex=b;Ma(d);l=0;for(q=a.aoColumns.length;l<q;l++){h=a.aoColumns[l];j=c?e[l]:P.createElement(h.sCellType);g.push(j);if(!c||h.mRender||h.mData!==l)j.innerHTML=y(a,b,l,"display");h.sClass&&(j.className+=" "+h.sClass);h.bVisible&&!c?i.appendChild(j):!h.bVisible&&c&&
|
||||
j.parentNode.removeChild(j);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,j,y(a,b,l),f,b,l)}w(a,"aoRowCreatedCallback",null,[i,f,b])}d.nTr.setAttribute("role","row")}function Ma(a){var b=a.nTr,c=a._aData;if(b){c.DT_RowId&&(b.id=c.DT_RowId);if(c.DT_RowClass){var e=c.DT_RowClass.split(" ");a.__rowc=a.__rowc?Na(a.__rowc.concat(e)):e;h(b).removeClass(a.__rowc.join(" ")).addClass(c.DT_RowClass)}c.DT_RowAttr&&h(b).attr(c.DT_RowAttr);c.DT_RowData&&h(b).data(c.DT_RowData)}}function jb(a){var b,c,e,d,
|
||||
f,g=a.nTHead,i=a.nTFoot,j=0===h("th, td",g).length,n=a.oClasses,l=a.aoColumns;j&&(d=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],e=h(f.nTh).addClass(f.sClass),j&&e.appendTo(d),a.oFeatures.bSort&&(e.addClass(f.sSortingClass),!1!==f.bSortable&&(e.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=e.html()&&e.html(f.sTitle),Pa(a,"header")(a,e,f,n);j&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);
|
||||
h(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var e,d,f,g=[],i=[],j=a.aoColumns.length,n;if(b){c===k&&(c=!1);e=0;for(d=b.length;e<d;e++){g[e]=b[e].slice();g[e].nTr=b[e].nTr;for(f=j-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[e].splice(f,1);i.push([])}e=0;for(d=g.length;e<d;e++){if(a=g[e].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[e].length;f<b;f++)if(n=
|
||||
j=1,i[e][f]===k){a.appendChild(g[e][f].cell);for(i[e][f]=1;g[e+j]!==k&&g[e][f].cell==g[e+j][f].cell;)i[e+j][f]=1,j++;for(;g[e][f+n]!==k&&g[e][f].cell==g[e][f+n].cell;){for(c=0;c<j;c++)i[e+c][f+n]=1;n++}h(g[e][f].cell).attr("rowspan",j).attr("colspan",n)}}}}function M(a){var b=w(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,e=a.asStripeClasses,d=e.length,f=a.oLanguage,g=a.iInitDisplayStart,i="ssp"==B(a),j=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=
|
||||
i?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(i){if(!a.bDestroying&&!kb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:g;i<f;i++){var l=j[i],q=a.aoData[l];null===q.nTr&&Ja(a,l);l=q.nTr;if(0!==d){var o=e[c%d];q._sRowStripe!=o&&(h(l).removeClass(q._sRowStripe).addClass(o),q._sRowStripe=o)}w(a,"aoRowCallback",null,[l,q._aData,c,i]);b.push(l);c++}}else c=f.sZeroRecords,
|
||||
1==a.iDraw&&"ajax"==B(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":d?e[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];w(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],La(a),g,n,j]);w(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],La(a),g,n,j]);e=h(a.nTBody);e.children().detach();e.append(h(b));w(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=
|
||||
!1}}function N(a,b){var c=a.oFeatures,e=c.bFilter;c.bSort&&lb(a);e?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;M(a);a._drawHold=!1}function mb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),e=a.oFeatures,d=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=d[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,i,j,n,l,q,o=0;o<f.length;o++){g=
|
||||
null;i=f[o];if("<"==i){j=h("<div/>")[0];n=f[o+1];if("'"==n||'"'==n){l="";for(q=2;f[o+q]!=n;)l+=f[o+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==l.charAt(0)?j.id=l.substr(1,l.length-1):j.className=l;o+=q}d.append(j);d=h(j)}else if(">"==i)d=d.parent();else if("l"==i&&e.bPaginate&&e.bLengthChange)g=nb(a);else if("f"==i&&e.bFilter)g=ob(a);else if("r"==i&&e.bProcessing)g=pb(a);else if("t"==i)g=qb(a);else if("i"==
|
||||
i&&e.bInfo)g=rb(a);else if("p"==i&&e.bPaginate)g=sb(a);else if(0!==m.ext.feature.length){j=m.ext.feature;q=0;for(n=j.length;q<n;q++)if(i==j[q].cFeature){g=j[q].fnInit(a);break}}g&&(j=a.aanFeatures,j[i]||(j[i]=[]),j[i].push(g),d.append(g))}c.replaceWith(d)}function da(a,b){var c=h(b).children("tr"),e,d,f,g,i,j,n,l,q,o;a.splice(0,a.length);f=0;for(j=c.length;f<j;f++)a.push([]);f=0;for(j=c.length;f<j;f++){e=c[f];for(d=e.firstChild;d;){if("TD"==d.nodeName.toUpperCase()||"TH"==d.nodeName.toUpperCase()){l=
|
||||
1*d.getAttribute("colspan");q=1*d.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(i=a[f];i[g];)g++;n=g;o=1===l?!0:!1;for(i=0;i<l;i++)for(g=0;g<q;g++)a[f+g][n+i]={cell:d,unique:o},a[f+g].nTr=e}d=d.nextSibling}}}function qa(a,b,c){var e=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,d=c.length;b<d;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!e[f]||!a.bSortCellsTop))e[f]=c[b][f].cell;return e}function ra(a,b,c){w(a,"aoServerParams","serverParams",[b]);
|
||||
if(b&&h.isArray(b)){var e={},d=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(d);c?(c=c[0],e[c]||(e[c]=[]),e[c].push(b.value)):e[b.name]=b.value});b=e}var f,g=a.ajax,i=a.oInstance,j=function(b){w(a,null,"xhr",[a,b]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&a.oApi._fnLog(a,0,c);a.json=b;j(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,
|
||||
c){var f=a.oApi._fnLog;"parsererror"==c?f(a,0,"Invalid JSON response",1):4===b.readyState&&f(a,0,"Ajax error",7);C(a,!1)}};a.oAjaxData=b;w(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(i,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),j,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(i,b,j,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function kb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,!0),ra(a,tb(a),function(b){ub(a,
|
||||
b)}),!1):!0}function tb(a){var b=a.aoColumns,c=b.length,e=a.oFeatures,d=a.oPreviousSearch,f=a.aoPreSearchCols,g,i=[],j,n,l,q=T(a);g=a._iDisplayStart;j=!1!==e.bPaginate?a._iDisplayLength:-1;var o=function(a,b){i.push({name:a,value:b})};o("sEcho",a.iDraw);o("iColumns",c);o("sColumns",D(b,"sName").join(","));o("iDisplayStart",g);o("iDisplayLength",j);var k={draw:a.iDraw,columns:[],order:[],start:g,length:j,search:{value:d.sSearch,regex:d.bRegex}};for(g=0;g<c;g++)n=b[g],l=f[g],j="function"==typeof n.mData?
|
||||
"function":n.mData,k.columns.push({data:j,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),o("mDataProp_"+g,j),e.bFilter&&(o("sSearch_"+g,l.sSearch),o("bRegex_"+g,l.bRegex),o("bSearchable_"+g,n.bSearchable)),e.bSort&&o("bSortable_"+g,n.bSortable);e.bFilter&&(o("sSearch",d.sSearch),o("bRegex",d.bRegex));e.bSort&&(h.each(q,function(a,b){k.order.push({column:b.col,dir:b.dir});o("iSortCol_"+a,b.col);o("sSortDir_"+a,b.dir)}),o("iSortingCols",q.length));
|
||||
b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?i:k:b?i:k}function ub(a,b){var c=sa(a,b),e=b.sEcho!==k?b.sEcho:b.draw,d=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(e){if(1*e<a.iDraw)return;a.iDraw=1*e}oa(a);a._iRecordsTotal=parseInt(d,10);a._iRecordsDisplay=parseInt(f,10);e=0;for(d=c.length;e<d;e++)J(a,c[e]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;M(a);a._bInitComplete||ta(a,b);a.bAjaxDataGet=!0;C(a,
|
||||
!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?W(c)(b):b}function ob(a){var b=a.oClasses,c=a.sTableId,e=a.oLanguage,d=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',i=e.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",g):i+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(i)),f=function(){var b=!this.value?"":this.value;b!=d.sSearch&&
|
||||
(fa(a,{sSearch:b,bRegex:d.bRegex,bSmart:d.bSmart,bCaseInsensitive:d.bCaseInsensitive}),a._iDisplayStart=0,M(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===B(a)?400:0,j=h("input",b).val(d.sSearch).attr("placeholder",e.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{j[0]!==P.activeElement&&j.val(d.sSearch)}catch(f){}});return b[0]}
|
||||
function fa(a,b,c){var e=a.oPreviousSearch,d=a.aoPreSearchCols,f=function(a){e.sSearch=a.sSearch;e.bRegex=a.bRegex;e.bSmart=a.bSmart;e.bCaseInsensitive=a.bCaseInsensitive};Ha(a);if("ssp"!=B(a)){vb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<d.length;b++)wb(a,d[b].sSearch,b,d[b].bEscapeRegex!==k?!d[b].bEscapeRegex:d[b].bRegex,d[b].bSmart,d[b].bCaseInsensitive);xb(a)}else f(b);a.bFiltered=!0;w(a,null,"search",[a])}function xb(a){for(var b=m.ext.search,
|
||||
c=a.aiDisplay,e,d,f=0,g=b.length;f<g;f++){for(var i=[],j=0,h=c.length;j<h;j++)d=c[j],e=a.aoData[d],b[f](a,e._aFilterData,d,e._aData,j)&&i.push(d);c.length=0;c.push.apply(c,i)}}function wb(a,b,c,e,d,f){if(""!==b)for(var g=a.aiDisplay,e=Qa(b,e,d,f),d=g.length-1;0<=d;d--)b=a.aoData[g[d]]._aFilterData[c],e.test(b)||g.splice(d,1)}function vb(a,b,c,e,d,f){var e=Qa(b,e,d,f),d=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=yb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||
|
||||
c||d.length>b.length||0!==b.indexOf(d)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)e.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,e){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||"",function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,e?"i":"")}function va(a){return a.replace(Yb,"\\$1")}function yb(a){var b=a.aoColumns,c,e,d,f,g,i,j,h,l=m.ext.type.search;
|
||||
c=!1;e=0;for(f=a.aoData.length;e<f;e++)if(h=a.aoData[e],!h._aFilterData){i=[];d=0;for(g=b.length;d<g;d++)c=b[d],c.bSearchable?(j=y(a,e,d,"filter"),l[c.sType]&&(j=l[c.sType](j)),null===j&&(j=""),"string"!==typeof j&&j.toString&&(j=j.toString())):j="",j.indexOf&&-1!==j.indexOf("&")&&(wa.innerHTML=j,j=Zb?wa.textContent:wa.innerText),j.replace&&(j=j.replace(/[\r\n]/g,"")),i.push(j);h._aFilterData=i;h._sFilterRow=i.join(" ");c=!0}return c}function zb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,
|
||||
caseInsensitive:a.bCaseInsensitive}}function Ab(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function rb(a){var b=a.sTableId,c=a.aanFeatures.i,e=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Bb,sName:"information"}),e.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return e[0]}function Bb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,e=a._iDisplayStart+
|
||||
1,d=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),i=g?c.sInfo:c.sInfoEmpty;g!==f&&(i+=" "+c.sInfoFiltered);i+=c.sInfoPostFix;i=Cb(a,i);c=c.fnInfoCallback;null!==c&&(i=c.call(a.oInstance,a,e,d,f,g,i));h(b).html(i)}}function Cb(a,b){var c=a.fnFormatNumber,e=a._iDisplayStart+1,d=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===d;return b.replace(/_START_/g,c.call(a,e)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,
|
||||
f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(e/d))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/d)))}function ga(a){var b,c,e=a.iInitDisplayStart,d=a.aoColumns,f;c=a.oFeatures;if(a.bInitialised){mb(a);jb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ga(a);b=0;for(c=d.length;b<c;b++)f=d[b],f.sWidth&&(f.nTh.style.width=s(f.sWidth));N(a);d=B(a);"ssp"!=d&&("ajax"==d?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)J(a,f[b]);a.iInitDisplayStart=e;N(a);C(a,!1);ta(a,c)},a):(C(a,!1),
|
||||
ta(a)))}else setTimeout(function(){ga(a)},200)}function ta(a,b){a._bInitComplete=!0;b&&X(a);w(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);w(a,null,"length",[a,c])}function nb(a){for(var b=a.oClasses,c=a.sTableId,e=a.aLengthMenu,d=h.isArray(e[0]),f=d?e[0]:e,e=d?e[1]:e,d=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,i=f.length;g<i;g++)d[0][g]=new Option(e[g],f[g]);var j=h("<div><label/></div>").addClass(b.sLength);
|
||||
a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",d[0].outerHTML));h("select",j).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());M(a)});h(a.nTable).bind("length.dt.DT",function(b,c,f){a===c&&h("select",j).val(f)});return j[0]}function sb(a){var b=a.sPaginationType,c=m.ext.pager[b],e="function"===typeof c,d=function(a){M(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;e||c.fnInit(a,b,d);f.p||(b.id=a.sTableId+
|
||||
"_paginate",a.aoDrawCallback.push({fn:function(a){if(e){var b=a._iDisplayStart,h=a._iDisplayLength,n=a.fnRecordsDisplay(),l=-1===h,b=l?0:Math.ceil(b/h),h=l?1:Math.ceil(n/h),n=c(b,h),q,l=0;for(q=f.p.length;l<q;l++)Pa(a,"pageButton")(a,f.p[l],l,n,b,h)}else c.fnUpdate(a,d)},sName:"pagination"}));return b}function Ta(a,b,c){var e=a._iDisplayStart,d=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===d?e=0:"number"===typeof b?(e=b*d,e>f&&(e=0)):"first"==b?e=0:"previous"==b?(e=0<=d?e-d:0,0>e&&(e=0)):"next"==
|
||||
b?e+d<f&&(e+=d):"last"==b?e=Math.floor((f-1)/d)*d:R(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==e;a._iDisplayStart=e;b&&(w(a,null,"page",[a]),c&&M(a));return b}function pb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");w(a,null,"processing",[a,b])}function qb(a){var b=h(a.nTable);b.attr("role",
|
||||
"grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var e=c.sX,d=c.sY,f=a.oClasses,g=b.children("caption"),i=g.length?g[0]._captionSide:null,j=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");l.length||(l=null);c=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:e?!e?null:s(e):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
|
||||
width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append("top"===i?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({overflow:"auto",height:!d?null:s(d),width:!e?null:s(e)}).append(b));l&&c.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:e?!e?null:s(e):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===i?g:null).append(b.children("tfoot")))));
|
||||
var b=c.children(),q=b[0],f=b[1],o=l?b[2]:null;if(e)h(f).on("scroll.DT",function(){var a=this.scrollLeft;q.scrollLeft=a;l&&(o.scrollLeft=a)});a.nScrollHead=q;a.nScrollBody=f;a.nScrollFoot=o;a.aoDrawCallback.push({fn:Y,sName:"scrolling"});return c[0]}function Y(a){var b=a.oScroll,c=b.sX,e=b.sXInner,d=b.sY,f=b.iBarWidth,g=h(a.nScrollHead),i=g[0].style,j=g.children("div"),n=j[0].style,l=j.children("table"),j=a.nScrollBody,q=h(j),o=j.style,k=h(a.nScrollFoot).children("div"),p=k.children("table"),m=h(a.nTHead),
|
||||
r=h(a.nTable),t=r[0],u=t.style,K=a.nTFoot?h(a.nTFoot):null,ha=a.oBrowser,w=ha.bScrollOversize,x,v,y,L,z,A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};r.children("thead, tfoot").remove();z=m.clone().prependTo(r);x=m.find("tr");y=z.find("tr");z.find("th, td").removeAttr("tabindex");K&&(L=K.clone().prependTo(r),v=K.find("tr"),L=L.find("tr"));c||(o.width="100%",g[0].style.width="100%");h.each(qa(a,z),function(b,c){D=
|
||||
la(a,b);c.style.width=a.aoColumns[D].sWidth});K&&G(function(a){a.style.width=""},L);b.bCollapse&&""!==d&&(o.height=q[0].offsetHeight+m[0].offsetHeight+"px");g=r.outerWidth();if(""===c){if(u.width="100%",w&&(r.find("tbody").height()>j.offsetHeight||"scroll"==q.css("overflow-y")))u.width=s(r.outerWidth()-f)}else""!==e?u.width=s(e):g==q.width()&&q.height()<r.height()?(u.width=s(g-f),r.outerWidth()>g-f&&(u.width=s(g))):u.width=s(g);g=r.outerWidth();G(E,y);G(function(a){C.push(a.innerHTML);A.push(s(h(a).css("width")))},
|
||||
y);G(function(a,b){a.style.width=A[b]},x);h(y).height(0);K&&(G(E,L),G(function(a){B.push(s(h(a).css("width")))},L),G(function(a,b){a.style.width=B[b]},v),h(L).height(0));G(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+C[b]+"</div>";a.style.width=A[b]},y);K&&G(function(a,b){a.innerHTML="";a.style.width=B[b]},L);if(r.outerWidth()<g){v=j.scrollHeight>j.offsetHeight||"scroll"==q.css("overflow-y")?g+f:g;if(w&&(j.scrollHeight>j.offsetHeight||"scroll"==q.css("overflow-y")))u.width=
|
||||
s(v-f);(""===c||""!==e)&&R(a,1,"Possible column misalignment",6)}else v="100%";o.width=s(v);i.width=s(v);K&&(a.nScrollFoot.style.width=s(v));!d&&w&&(o.height=s(t.offsetHeight+f));d&&b.bCollapse&&(o.height=s(d),b=c&&t.offsetWidth>j.offsetWidth?f:0,t.offsetHeight<j.offsetHeight&&(o.height=s(t.offsetHeight+b)));b=r.outerWidth();l[0].style.width=s(b);n.width=s(b);l=r.height()>j.clientHeight||"scroll"==q.css("overflow-y");ha="padding"+(ha.bScrollbarLeft?"Left":"Right");n[ha]=l?f+"px":"0px";K&&(p[0].style.width=
|
||||
s(b),k[0].style.width=s(b),k[0].style[ha]=l?f+"px":"0px");q.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}function G(a,b,c){for(var e=0,d=0,f=b.length,g,i;d<f;){g=b[d].firstChild;for(i=c?c[d].firstChild:null;g;)1===g.nodeType&&(c?a(g,i,e):a(g,e),e++),g=g.nextSibling,i=c?i.nextSibling:null;d++}}function Ga(a){var b=a.nTable,c=a.aoColumns,e=a.oScroll,d=e.sY,f=e.sX,g=e.sXInner,i=c.length,e=Z(a,"bVisible"),j=h("th",a.nTHead),n=b.getAttribute("width"),l=b.parentNode,k=!1,o,m;(o=b.style.width)&&
|
||||
-1!==o.indexOf("%")&&(n=o);for(o=0;o<e.length;o++)m=c[e[o]],null!==m.sWidth&&(m.sWidth=Db(m.sWidthOrig,l),k=!0);if(!k&&!f&&!d&&i==aa(a)&&i==j.length)for(o=0;o<i;o++)c[o].sWidth=s(j.eq(o).width());else{i=h(b).clone().empty().css("visibility","hidden").removeAttr("id").append(h(a.nTHead).clone(!1)).append(h(a.nTFoot).clone(!1)).append(h("<tbody><tr/></tbody>"));i.find("tfoot th, tfoot td").css("width","");var p=i.find("tbody tr"),j=qa(a,i.find("thead")[0]);for(o=0;o<e.length;o++)m=c[e[o]],j[o].style.width=
|
||||
null!==m.sWidthOrig&&""!==m.sWidthOrig?s(m.sWidthOrig):"";if(a.aoData.length)for(o=0;o<e.length;o++)k=e[o],m=c[k],h(Eb(a,k)).clone(!1).append(m.sContentPadding).appendTo(p);i.appendTo(l);f&&g?i.width(g):f?(i.css("width","auto"),i.width()<l.offsetWidth&&i.width(l.offsetWidth)):d?i.width(l.offsetWidth):n&&i.width(n);Fb(a,i[0]);if(f){for(o=g=0;o<e.length;o++)m=c[e[o]],d=h(j[o]).outerWidth(),g+=null===m.sWidthOrig?d:parseInt(m.sWidth,10)+d-h(j[o]).width();i.width(s(g));b.style.width=s(g)}for(o=0;o<e.length;o++)if(m=
|
||||
c[e[o]],d=h(j[o]).width())m.sWidth=s(d);b.style.width=s(i.css("width"));i.remove()}n&&(b.style.width=s(n));if((n||f)&&!a._reszEvt)h(Ea).bind("resize.DT-"+a.sInstance,ua(function(){X(a)})),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,e,d;return function(){var b=this,g=+new Date,i=arguments;e&&g<e+c?(clearTimeout(d),d=setTimeout(function(){e=k;a.apply(b,i)},c)):(e=g,a.apply(b,i))}}function Db(a,b){if(!a)return 0;var c=h("<div/>").css("width",s(a)).appendTo(b||P.body),e=c[0].offsetWidth;c.remove();
|
||||
return e}function Fb(a,b){var c=a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(h(b).outerWidth()-c)}function Eb(a,b){var c=Gb(a,b);if(0>c)return null;var e=a.aoData[c];return!e.nTr?h("<td/>").html(y(a,c,b,"display"))[0]:e.anCells[b]}function Gb(a,b){for(var c,e=-1,d=-1,f=0,g=a.aoData.length;f<g;f++)c=y(a,f,b,"display")+"",c=c.replace($b,""),c.length>e&&(e=c.length,d=f);return d}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Hb(){var a=
|
||||
m.__scrollbarWidth;if(a===k){var b=h("<p/>").css({position:"absolute",top:0,left:0,width:"100%",height:150,padding:0,overflow:"scroll",visibility:"hidden"}).appendTo("body"),a=b[0].offsetWidth-b[0].clientWidth;m.__scrollbarWidth=a;b.remove()}return a}function T(a){var b,c,e=[],d=a.aoColumns,f,g,i,j;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):n.push.apply(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<
|
||||
n.length;a++){j=n[a][0];f=d[j].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],i=d[g].sType||"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],d[g].asSorting)),e.push({src:j,col:g,dir:n[a][1],index:n[a]._idx,type:i,formatter:m.ext.type.order[i+"-pre"]})}return e}function lb(a){var b,c,e=[],d=m.ext.type.order,f=a.aoData,g=0,i,h=a.aiDisplayMaster,n;Ha(a);n=T(a);b=0;for(c=n.length;b<c;b++)i=n[b],i.formatter&&g++,Ib(a,i.col);if("ssp"!=B(a)&&0!==n.length){b=0;for(c=h.length;b<c;b++)e[h[b]]=b;g===n.length?
|
||||
h.sort(function(a,b){var c,d,g,h,i=n.length,j=f[a]._aSortData,k=f[b]._aSortData;for(g=0;g<i;g++)if(h=n[g],c=j[h.col],d=k[h.col],c=c<d?-1:c>d?1:0,0!==c)return"asc"===h.dir?c:-c;c=e[a];d=e[b];return c<d?-1:c>d?1:0}):h.sort(function(a,b){var c,g,h,i,j=n.length,k=f[a]._aSortData,m=f[b]._aSortData;for(h=0;h<j;h++)if(i=n[h],c=k[i.col],g=m[i.col],i=d[i.type+"-"+i.dir]||d["string-"+i.dir],c=i(c,g),0!==c)return c;c=e[a];g=e[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,e=a.aoColumns,d=
|
||||
T(a),a=a.oLanguage.oAria,f=0,g=e.length;f<g;f++){c=e[f];var i=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var h=c.nTh;h.removeAttribute("aria-sort");c.bSortable&&(0<d.length&&d[0].col==f?(h.setAttribute("aria-sort","asc"==d[0].dir?"ascending":"descending"),c=i[d[0].index+1]||i[0]):c=i[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);h.setAttribute("aria-label",b)}}function Ua(a,b,c,e){var d=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+
|
||||
1<f.length?c+1:b?null:0};"number"===typeof d[0]&&(d=a.aaSorting=[d]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,D(d,"0")),-1!==c?(b=g(d[c],!0),null===b&&1===d.length&&(b=0),null===b?d.splice(c,1):(d[c][1]=f[b],d[c]._idx=b)):(d.push([b,f[0],0]),d[d.length-1]._idx=0)):d.length&&d[0][0]==b?(b=g(d[0]),d.length=1,d[0][1]=f[b],d[0]._idx=b):(d.length=0,d.push([b,f[0]]),d[0]._idx=0);N(a);"function"==typeof e&&e(a)}function Oa(a,b,c,e){var d=a.aoColumns[c];Va(b,{},function(b){!1!==d.bSortable&&(a.oFeatures.bProcessing?
|
||||
(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,e);"ssp"!==B(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,e))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,e=T(a),d=a.oFeatures,f,g;if(d.bSort&&d.bSortClasses){d=0;for(f=b.length;d<f;d++)g=b[d].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>d?d+1:3));d=0;for(f=e.length;d<f;d++)g=e[d].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>d?d+1:3))}a.aLastSort=e}function Ib(a,b){var c=a.aoColumns[b],e=m.ext.order[c.sSortDataType],d;e&&(d=e.call(a.oInstance,
|
||||
a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],h=0,j=a.aoData.length;h<j;h++)if(c=a.aoData[h],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||e)f=e?d[h]:y(a,h,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:zb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,e){return{visible:b.bVisible,search:zb(a.aoPreSearchCols[e])}})};w(a,
|
||||
"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,e=a.aoColumns;if(a.oFeatures.bStateSave){var d=a.fnStateLoadCallback.call(a.oInstance,a);if(d&&d.time&&(b=w(a,"aoStateLoadParams","stateLoadParams",[a,d]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&d.time<+new Date-1E3*b)&&e.length===d.columns.length))){a.oLoadedState=h.extend(!0,{},d);d.start!==k&&(a._iDisplayStart=d.start,a.iInitDisplayStart=d.start);d.length!==
|
||||
k&&(a._iDisplayLength=d.length);d.order!==k&&(a.aaSorting=[],h.each(d.order,function(b,c){a.aaSorting.push(c[0]>=e.length?[0,c[1]]:c)}));d.search!==k&&h.extend(a.oPreviousSearch,Ab(d.search));b=0;for(c=d.columns.length;b<c;b++){var f=d.columns[b];f.visible!==k&&(e[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],Ab(f.search))}w(a,"aoStateLoaded","stateLoaded",[a,d])}}}function za(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function R(a,b,c,e){c="DataTables warning: "+
|
||||
(null!==a?"table id="+a.sTableId+" - ":"")+c;e&&(c+=". For more information about this error, please see http://datatables.net/tn/"+e);if(b)Ea.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,w(a,null,"error",[a,e,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,e,c)}}function E(a,b,c,e){h.isArray(c)?h.each(c,function(c,f){h.isArray(f)?E(a,b,f[0],f[1]):E(a,b,f)}):(e===k&&(e=c),b[c]!==k&&(a[e]=b[c]))}function Lb(a,b,c){var e,d;for(d in b)b.hasOwnProperty(d)&&
|
||||
(e=b[d],h.isPlainObject(e)?(h.isPlainObject(a[d])||(a[d]={}),h.extend(!0,a[d],e)):a[d]=c&&"data"!==d&&"aaData"!==d&&h.isArray(e)?e.slice():e);return a}function Va(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function z(a,b,c,e){c&&a[b].push({fn:c,sName:e})}function w(a,b,c,e){var d=[];b&&(d=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,e)}));
|
||||
null!==c&&h(a.nTable).trigger(c+".dt",e);return d}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),e=a._iDisplayLength;b>=c&&(b=c-e);b-=b%e;if(-1===e||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,e=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?e[c[b]]||e._:"string"===typeof c?e[c]||e._:e._}function B(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Wa(a,b){var c=[],c=Mb.numbers_length,e=Math.floor(c/2);b<=c?c=U(0,b):a<=e?(c=U(0,c-2),c.push("ellipsis"),
|
||||
c.push(b-1)):(a>=b-1-e?c=U(b-(c-2),b):(c=U(a-e+2,a+e-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return Aa(b,a)},"num-fmt":function(b){return Aa(b,a,Xa)},"html-num":function(b){return Aa(b,a,Ba)},"html-num-fmt":function(b){return Aa(b,a,Ba,Xa)}},function(b,c){x.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(x.type.search[b+a]=x.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));
|
||||
return m.ext.internal[a].apply(this,b)}}var m,x,t,r,u,Ya={},Ob=/[\r\n]/g,Ba=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,Yb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$\u00a3\u20ac\u00a5%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,I=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Ya[b],
|
||||
"."):a},Za=function(a,b,c){var e="string"===typeof a;if(I(a))return!0;b&&e&&(a=Qb(a,b));c&&e&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return I(a)?!0:!(I(a)||"string"===typeof a)?null:Za(a.replace(Ba,""),b,c)?!0:null},D=function(a,b,c){var e=[],d=0,f=a.length;if(c!==k)for(;d<f;d++)a[d]&&a[d][b]&&e.push(a[d][b][c]);else for(;d<f;d++)a[d]&&e.push(a[d][b]);return e},ia=function(a,b,c,e){var d=[],f=0,g=b.length;if(e!==k)for(;f<g;f++)a[b[f]][c]&&d.push(a[b[f]][c][e]);
|
||||
else for(;f<g;f++)d.push(a[b[f]][c]);return d},U=function(a,b){var c=[],e;b===k?(b=0,e=a):(e=b,b=a);for(var d=b;d<e;d++)c.push(d);return c},Sb=function(a){for(var b=[],c=0,e=a.length;c<e;c++)a[c]&&b.push(a[c]);return b},Na=function(a){var b=[],c,e,d=a.length,f,g=0;e=0;a:for(;e<d;e++){c=a[e];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,S=/\(\)$/,wa=h("<div>")[0],Zb=wa.textContent!==k,$b=/<.*?>/g;m=function(a){this.$=function(a,
|
||||
b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(za(this[x.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),e=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return e.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],e=c.oScroll;a===k||a?b.draw(!1):(""!==e.sX||""!==e.sY)&&Y(c)};this.fnClearTable=
|
||||
function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var e=this.api(!0),a=e.rows(a),d=a.settings()[0],h=d.aoData[a[0][0]];a.remove();b&&b.call(this,d,h);(c===k||c)&&e.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,e,d,h){d=this.api(!0);null===b||b===k?d.search(a,c,e,h):d.column(b).search(a,c,e,h);d.draw()};
|
||||
this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var e=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==e||"th"==e?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};
|
||||
this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return za(this[x.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,
|
||||
b,c)};this.fnUpdate=function(a,b,c,e,d){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(d===k||d)&&h.columns.adjust();(e===k||e)&&h.draw();return 0};this.fnVersionCheck=x.fnVersionCheck;var b=this,c=a===k,e=this.length;c&&(a={});this.oApi=this.internal=x.internal;for(var d in m.ext.internal)d&&(this[d]=Nb(d));this.each(function(){var d={},d=1<e?Lb(d,a,!0):a,g=0,i,j=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())R(null,0,"Non-table node initialisation ("+
|
||||
this.nodeName+")",2);else{eb(l);fb(l.column);H(l,l,!0);H(l.column,l.column,!0);H(l,h.extend(d,q.data()));var o=m.settings,g=0;for(i=o.length;g<i;g++){var r=o[g];if(r.nTable==this||r.nTHead.parentNode==this||r.nTFoot&&r.nTFoot.parentNode==this){g=d.bRetrieve!==k?d.bRetrieve:l.bRetrieve;if(c||g)return r.oInstance;if(d.bDestroy!==k?d.bDestroy:l.bDestroy){r.oInstance.fnDestroy();break}else{R(r,0,"Cannot reinitialise DataTable",3);return}}if(r.sTableId==this.id){o.splice(g,1);break}}if(null===j||""===
|
||||
j)this.id=j="DataTables_Table_"+m.ext._unique++;var p=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:j,sTableId:j});p.nTable=this;p.oApi=b.internal;p.oInit=d;o.push(p);p.oInstance=1===b.length?b:q.dataTable();eb(d);d.oLanguage&&O(d.oLanguage);d.aLengthMenu&&!d.iDisplayLength&&(d.iDisplayLength=h.isArray(d.aLengthMenu[0])?d.aLengthMenu[0][0]:d.aLengthMenu[0]);d=Lb(h.extend(!0,{},l),d);E(p.oFeatures,d,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));
|
||||
E(p,d,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);E(p.oScroll,d,[["sScrollX","sX"],["sScrollXInner",
|
||||
"sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);E(p.oLanguage,d,"fnInfoCallback");z(p,"aoDrawCallback",d.fnDrawCallback,"user");z(p,"aoServerParams",d.fnServerParams,"user");z(p,"aoStateSaveParams",d.fnStateSaveParams,"user");z(p,"aoStateLoadParams",d.fnStateLoadParams,"user");z(p,"aoStateLoaded",d.fnStateLoaded,"user");z(p,"aoRowCallback",d.fnRowCallback,"user");z(p,"aoRowCreatedCallback",d.fnCreatedRow,"user");z(p,"aoHeaderCallback",d.fnHeaderCallback,"user");z(p,"aoFooterCallback",
|
||||
d.fnFooterCallback,"user");z(p,"aoInitComplete",d.fnInitComplete,"user");z(p,"aoPreDrawCallback",d.fnPreDrawCallback,"user");j=p.oClasses;d.bJQueryUI?(h.extend(j,m.ext.oJUIClasses,d.oClasses),d.sDom===l.sDom&&"lfrtip"===l.sDom&&(p.sDom='<"H"lfr>t<"F"ip>'),p.renderer)?h.isPlainObject(p.renderer)&&!p.renderer.header&&(p.renderer.header="jqueryui"):p.renderer="jqueryui":h.extend(j,m.ext.classes,d.oClasses);q.addClass(j.sTable);if(""!==p.oScroll.sX||""!==p.oScroll.sY)p.oScroll.iBarWidth=Hb();!0===p.oScroll.sX&&
|
||||
(p.oScroll.sX="100%");p.iInitDisplayStart===k&&(p.iInitDisplayStart=d.iDisplayStart,p._iDisplayStart=d.iDisplayStart);null!==d.iDeferLoading&&(p.bDeferLoading=!0,g=h.isArray(d.iDeferLoading),p._iRecordsDisplay=g?d.iDeferLoading[0]:d.iDeferLoading,p._iRecordsTotal=g?d.iDeferLoading[1]:d.iDeferLoading);var t=p.oLanguage;h.extend(!0,t,d.oLanguage);""!==t.sUrl&&(h.ajax({dataType:"json",url:t.sUrl,success:function(a){O(a);H(l.oLanguage,a);h.extend(true,t,a);ga(p)},error:function(){ga(p)}}),n=!0);null===
|
||||
d.asStripeClasses&&(p.asStripeClasses=[j.sStripeOdd,j.sStripeEven]);var g=p.asStripeClasses,s=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return s.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),p.asDestroyStripes=g.slice());o=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(p.aoHeader,g[0]),o=qa(p));if(null===d.aoColumns){r=[];g=0;for(i=o.length;g<i;g++)r.push(null)}else r=d.aoColumns;g=0;for(i=r.length;g<i;g++)Fa(p,o?o[g]:null);ib(p,d.aoColumnDefs,
|
||||
r,function(a,b){ka(p,a,b)});if(s.length){var u=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h.each(na(p,s[0]).cells,function(a,b){var c=p.aoColumns[a];if(c.mData===a){var d=u(b,"sort")||u(b,"order"),e=u(b,"filter")||u(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ka(p,a)}}})}var v=p.oFeatures;d.bStateSave&&(v.bStateSave=!0,Kb(p,d),z(p,"aoDrawCallback",ya,"state_save"));if(d.aaSorting===
|
||||
k){o=p.aaSorting;g=0;for(i=o.length;g<i;g++)o[g][1]=p.aoColumns[g].asSorting[0]}xa(p);v.bSort&&z(p,"aoDrawCallback",function(){if(p.bSorted){var a=T(p),b={};h.each(a,function(a,c){b[c.src]=c.dir});w(p,null,"order",[p,a,b]);Jb(p)}});z(p,"aoDrawCallback",function(){(p.bSorted||B(p)==="ssp"||v.bDeferRender)&&xa(p)},"sc");gb(p);g=q.children("caption").each(function(){this._captionSide=q.css("caption-side")});i=q.children("thead");0===i.length&&(i=h("<thead/>").appendTo(this));p.nTHead=i[0];i=q.children("tbody");
|
||||
0===i.length&&(i=h("<tbody/>").appendTo(this));p.nTBody=i[0];i=q.children("tfoot");if(0===i.length&&0<g.length&&(""!==p.oScroll.sX||""!==p.oScroll.sY))i=h("<tfoot/>").appendTo(this);0===i.length||0===i.children().length?q.addClass(j.sNoFooter):0<i.length&&(p.nTFoot=i[0],da(p.aoFooter,p.nTFoot));if(d.aaData)for(g=0;g<d.aaData.length;g++)J(p,d.aaData[g]);else(p.bDeferLoading||"dom"==B(p))&&ma(p,h(p.nTBody).children("tr"));p.aiDisplay=p.aiDisplayMaster.slice();p.bInitialised=!0;!1===n&&ga(p)}});b=null;
|
||||
return this};var Tb=[],v=Array.prototype,cc=function(a){var b,c,e=m.settings,d=h.map(e,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,d),-1!==b?[e[b]]:null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,d);return-1!==b?e[b]:null}).toArray()};t=function(a,b){if(!this instanceof t)throw"DT API must be constructed as a new object";
|
||||
var c=[],e=function(a){(a=cc(a))&&c.push.apply(c,a)};if(h.isArray(a))for(var d=0,f=a.length;d<f;d++)e(a[d]);else e(a);this.context=Na(c);b&&this.push.apply(this,b.toArray?b.toArray():b);this.selector={rows:null,cols:null,opts:null};t.extend(this,this,Tb)};m.Api=t;t.prototype={concat:v.concat,context:[],each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];
|
||||
if(v.filter)b=v.filter.call(this,a,this);else for(var c=0,e=this.length;c<e;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=[];return new t(this.context,a.concat.apply(a,this.toArray()))},join:v.join,indexOf:v.indexOf||function(a,b){for(var c=b||0,e=this.length;c<e;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,e){var d=[],f,g,h,j,n,l=this.context,q,o,m=this.selector;"string"===typeof a&&(e=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<
|
||||
h;g++){var p=new t(l[g]);if("table"===b)f=c.call(p,l[g],g),f!==k&&d.push(f);else if("columns"===b||"rows"===b)f=c.call(p,l[g],this[g],g),f!==k&&d.push(f);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){o=this[g];"column-rows"===b&&(q=Ca(l[g],m.opts));j=0;for(n=o.length;j<n;j++)f=o[j],f="cell"===b?c.call(p,l[g],f.row,f.column,g,j):c.call(p,l[g],f,g,j,q),f!==k&&d.push(f)}}return d.length||e?(a=new t(l,a?d.concat.apply([],d):d),b=a.selector,b.rows=m.rows,b.cols=m.cols,b.opts=m.opts,a):
|
||||
this},lastIndexOf:v.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(v.map)b=v.map.call(this,a,this);else for(var c=0,e=this.length;c<e;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:v.pop,push:v.push,reduce:v.reduce||function(a,b){return hb(this,a,b,0,this.length,1)},reduceRight:v.reduceRight||function(a,b){return hb(this,a,b,this.length-1,
|
||||
-1,-1)},reverse:v.reverse,selector:null,shift:v.shift,sort:v.sort,splice:v.splice,toArray:function(){return v.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new t(this.context,Na(this))},unshift:v.unshift};t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var e,d,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};e=0;for(d=c.length;e<d;e++)f=c[e],b[f.name]="function"===
|
||||
typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=r=function(a,b){if(h.isArray(a))for(var c=0,e=a.length;c<e;c++)t.register(a[c],b);else for(var d=a.split("."),f=Tb,g,i,c=0,e=d.length;c<e;c++){g=(i=-1!==d[c].indexOf("()"))?d[c].replace("()",""):d[c];var j;a:{j=0;for(var n=f.length;j<n;j++)if(f[j].name===g){j=f[j];break a}j=null}j||(j={name:g,val:{},methodExt:[],propExt:[]},f.push(j));c===e-1?j.val=b:f=i?j.methodExt:j.propExt}};
|
||||
t.registerPlural=u=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,a[0]):a[0]:k:a})};r("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var e=h.map(c,function(a){return a.nTable}),a=h(e).filter(a).map(function(){var a=h.inArray(this,e);return c[a]}).toArray();b=new b(a)}else b=this;return b});r("table()",function(a){var a=this.tables(a),b=
|
||||
a.context;return b.length?new t(b[0]):a});u("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});u("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});u("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});u("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});u("tables().containers()",
|
||||
"table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});r("draw()",function(a){return this.iterator("table",function(b){N(b,!1===a)})});r("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});r("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,e=a.fnRecordsDisplay(),d=-1===c;return{page:d?0:Math.floor(b/c),pages:d?1:Math.ceil(e/c),start:b,
|
||||
end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:e}});r("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var e=new t(a);e.one("draw",function(){c(e.ajax.json())})}"ssp"==B(a)?N(a,b):(C(a,!0),ra(a,[],function(c){oa(a);for(var c=sa(a,c),e=0,g=c.length;e<g;e++)J(a,c[e]);N(a,b);C(a,!1)}))};r("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});
|
||||
r("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});r("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});r("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});r("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,
|
||||
!1===b,a)})});var $a=function(a,b){var c=[],e,d,f,g,i,j;e=typeof a;if(!a||"string"===e||"function"===e||a.length===k)a=[a];f=0;for(g=a.length;f<g;f++){d=a[f]&&a[f].split?a[f].split(","):[a[f]];i=0;for(j=d.length;i<j;i++)(e=b("string"===typeof d[i]?h.trim(d[i]):d[i]))&&e.length&&c.push.apply(c,e)}return c},ab=function(a){a||(a={});a.filter&&!a.search&&(a.search=a.filter);return{search:a.search||"none",order:a.order||"current",page:a.page||"all"}},bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<
|
||||
a[b].length)return a[0]=a[b],a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Ca=function(a,b){var c,e,d,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var i=b.search;e=b.order;d=b.page;if("ssp"==B(a))return"removed"===i?[]:U(0,c.length);if("current"==d){c=a._iDisplayStart;for(e=a.fnDisplayEnd();c<e;c++)f.push(g[c])}else if("current"==e||"applied"==e)f="none"==i?c.slice():"applied"==i?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==e||"original"==e){c=0;for(e=a.aoData.length;c<
|
||||
e;c++)"none"==i?f.push(c):(d=h.inArray(c,g),(-1===d&&"removed"==i||0<=d&&"applied"==i)&&f.push(c))}return f};r("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var d=b;return $a(a,function(a){var b=Pb(a);if(b!==null&&!d)return[b];var i=Ca(c,d);if(b!==null&&h.inArray(b,i)!==-1)return[b];if(!a)return i;if(typeof a==="function")return h.map(i,function(b){var d=c.aoData[b];return a(b,d._aData,d.nTr)?b:null});b=Sb(ia(c.aoData,i,"nTr"));return a.nodeName&&
|
||||
h.inArray(a,b)!==-1?[a._DT_RowIndex]:h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()})},1);c.selector.rows=a;c.selector.opts=b;return c});r("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});r("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ia(a.aoData,b,"_aData")},1)});u("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var e=b.aoData[c];return"search"===a?e._aFilterData:
|
||||
e._aSortData},1)});u("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ca(b,c,a)})});u("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});u("rows().remove()","row().remove()",function(){var a=this;return this.iterator("row",function(b,c,e){var d=b.aoData;d.splice(c,1);for(var f=0,g=d.length;f<g;f++)null!==d[f].nTr&&(d[f].nTr._DT_RowIndex=f);h.inArray(c,b.aiDisplay);pa(b.aiDisplayMaster,c);pa(b.aiDisplay,
|
||||
c);pa(a[e],c,!1);Sa(b)})});r("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(J(b,c));return h},1),c=this.rows(-1);c.pop();c.push.apply(c,b.toArray());return c});r("row()",function(a,b){return bb(this.rows(a,b))});r("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],
|
||||
this[0],"data");return this});r("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});r("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:J(b,a)});return this.row(b[0])});var cb=function(a,b){var c=a.context;c.length&&(c=c[0].aoData[b!==k?b:a[0]],c._details&&(c._details.remove(),c._detailsShow=k,c._details=k))},Vb=function(a,b){var c=
|
||||
a.context;if(c.length&&a.length){var e=c[0].aoData[a[0]];if(e._details){(e._detailsShow=b)?e._details.insertAfter(e.nTr):e._details.detach();var d=c[0],f=new t(d),g=d.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){d===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(d===b)for(var c,
|
||||
e=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",e)}),f.on("destroy.dt.DT_details",function(a,b){if(d===b)for(var c=0,e=g.length;c<e;c++)g[c]._details&&cb(f,c)}))}}};r("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)cb(this);else if(c.length&&this.length){var e=c[0],c=c[0].aoData[this[0]],d=[],f=function(a,b){if(h.isArray(a)||a instanceof
|
||||
h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?d.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(e),d.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(d);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});r(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});r(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});r(["row().child.remove()",
|
||||
"row().child().remove()"],function(){cb(this);return this});r("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var dc=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,e,d){for(var c=[],e=0,f=d.length;e<f;e++)c.push(y(a,d[e],b));return c};r("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var d=a,f=b,g=c.aoColumns,i=D(g,"sName"),j=D(g,"nTh");return $a(d,function(a){var b=
|
||||
Pb(a);if(a==="")return U(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var d=Ca(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,d),j[f])?f:null})}var k=typeof a==="string"?a.match(dc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[la(c,b)];case "name":return h.map(i,function(a,b){return a===k[1]?b:null})}else return h(j).filter(a).map(function(){return h.inArray(this,
|
||||
j)}).toArray()})},1);c.selector.cols=a;c.selector.opts=b;return c});u("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});u("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});u("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});u("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",
|
||||
function(a,b){return a.aoColumns[b].mData},1)});u("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,e,d,f){return ia(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});u("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,e,d){return ia(a.aoData,d,"anCells",b)},1)});u("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,e){if(a===k)return c.aoColumns[e].bVisible;
|
||||
var d=c.aoColumns,f=d[e],g=c.aoData,i,j,n;if(a!==k&&f.bVisible!==a){if(a){var l=h.inArray(!0,D(d,"bVisible"),e+1);i=0;for(j=g.length;i<j;i++)n=g[i].nTr,d=g[i].anCells,n&&n.insertBefore(d[e],d[l]||null)}else h(D(c.aoData,"anCells",e)).detach();f.bVisible=a;ea(c,c.aoHeader);ea(c,c.aoFooter);if(b===k||b)X(c),(c.oScroll.sX||c.oScroll.sY)&&Y(c);w(c,null,"column-visibility",[c,e,a]);ya(c)}})});u("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===
|
||||
a?$(b,c):c},1)});r("columns.adjust()",function(){return this.iterator("table",function(a){X(a)},1)});r("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return la(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});r("column()",function(a,b){return bb(this.columns(a,b))});r("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",
|
||||
function(b){var d=a,e=ab(c),f=b.aoData,g=Ca(b,e),e=Sb(ia(f,g,"anCells")),i=h([].concat.apply([],e)),j,l=b.aoColumns.length,n,m,r,t,s,u;return $a(d,function(a){var c=typeof a==="function";if(a===null||a===k||c){n=[];m=0;for(r=g.length;m<r;m++){j=g[m];for(t=0;t<l;t++){s={row:j,column:t};if(c){u=b.aoData[j];a(s,y(b,j,t),u.anCells[t])&&n.push(s)}else n.push(s)}}return n}return h.isPlainObject(a)?[a]:i.filter(a).map(function(a,b){j=b.parentNode._DT_RowIndex;return{row:j,column:h.inArray(b,f[j].anCells)}}).toArray()})});
|
||||
var e=this.columns(b,c),d=this.rows(a,c),f,g,i,j,n,l=this.iterator("table",function(a,b){f=[];g=0;for(i=d[b].length;g<i;g++){j=0;for(n=e[b].length;j<n;j++)f.push({row:d[b][g],column:e[b][j]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});u("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b].anCells)?a[c]:k},1)});r("cells().data()",function(){return this.iterator("cell",function(a,b,c){return y(a,b,c)},1)});u("cells().cache()",
|
||||
"cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,e){return b.aoData[c][a][e]},1)});u("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,e){return y(b,c,e,a)},1)});u("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});u("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,
|
||||
c,e){ca(b,c,a,e)})});r("cell()",function(a,b,c){return bb(this.cells(a,b,c))});r("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?y(b[0],c[0].row,c[0].column):k;Ia(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});r("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=
|
||||
a.slice()})});r("order.listener()",function(a,b,c){return this.iterator("table",function(e){Oa(e,a,b,c)})});r(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,e){var d=[];h.each(b[e],function(b,c){d.push([c,a])});c.aaSorting=d})});r("search()",function(a,b,c,e){var d=this.context;return a===k?0!==d.length?d[0].oPreviousSearch.sSearch:k:this.iterator("table",function(d){d.oFeatures.bFilter&&fa(d,h.extend({},d.oPreviousSearch,{sSearch:a+"",bRegex:null===
|
||||
b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===e?!0:e}),1)})});u("columns().search()","column().search()",function(a,b,c,e){return this.iterator("column",function(d,f){var g=d.aoPreSearchCols;if(a===k)return g[f].sSearch;d.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===e?!0:e}),fa(d,d.oPreviousSearch,1))})});r("state()",function(){return this.context.length?this.context[0].oSavedState:null});r("state.clear()",function(){return this.iterator("table",
|
||||
function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});r("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});r("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,e,d=0,f=a.length;d<f;d++)if(c=parseInt(b[d],10)||0,e=parseInt(a[d],10)||0,c!==e)return c>e;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,
|
||||
function(a,d){var f=d.nScrollHead?h("table",d.nScrollHead)[0]:null,g=d.nScrollFoot?h("table",d.nScrollFoot)[0]:null;if(d.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){return h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable})};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=H;r("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,
|
||||
b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var e=h(this.tables().nodes());e[b].apply(e,a);return this})});r("clear()",function(){return this.iterator("table",function(a){oa(a)})});r("settings()",function(){return new t(this.context,this.context)});r("init()",function(){var a=this.context;return a.length?a[0].oInit:null});r("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});r("destroy()",
|
||||
function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,e=b.oClasses,d=b.nTable,f=b.nTBody,g=b.nTHead,i=b.nTFoot,j=h(d),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),q;b.bDestroying=!0;w(b,"aoDestroyCallback","destroy",[b]);a||(new t(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Ea).unbind(".DT-"+b.sInstance);d!=g.parentNode&&(j.children("thead").detach(),j.append(g));i&&d!=i.parentNode&&(j.children("tfoot").detach(),
|
||||
j.append(i));j.detach();k.detach();b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(e.sSortable+" "+e.sSortableAsc+" "+e.sSortableDesc+" "+e.sSortableNone);b.bJUI&&(h("th span."+e.sSortIcon+", td span."+e.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+e.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(d,b.nTableReinsertBefore);f.children().detach();f.append(l);j.css("width",b.sDestroyWidth).removeClass(e.sTable);
|
||||
(q=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%q])});c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){r(b+"s().every()",function(a){return this.iterator(b,function(e,d,f){a.call((new t(e))[b](d,f))})})});m.version="1.10.6";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,
|
||||
_sFilterRow:null,_sRowStripe:"",src:null};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],
|
||||
ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},
|
||||
fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,
|
||||
JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",
|
||||
sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};V(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,
|
||||
asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};V(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,
|
||||
iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],
|
||||
aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,
|
||||
_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==B(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==B(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,e=this.aiDisplay.length,d=this.oFeatures,f=d.bPaginate;return d.bServerSide?
|
||||
!1===f||-1===a?b+e:Math.min(b+a,this._iRecordsDisplay):!f||c>e||-1===a?e:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};m.ext=x={buttons:{},classes:{},errMode:"alert",feature:[],search:[],internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(x,{afnFiltering:x.search,aTypes:x.type.detect,
|
||||
ofnSearch:x.type.search,oSort:x.type.order,afnSortData:x.order,aoFeatures:x.feature,oApi:x.internal,oStdClasses:x.classes,oPagination:x.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",
|
||||
sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",
|
||||
sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Da="",Da="",F=Da+"ui-state-default",ja=Da+"css_right ui-icon ui-icon-",Xb=Da+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,m.ext.classes,{sPageButton:"fg-button ui-button "+F,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",
|
||||
sSortAsc:F+" sorting_asc",sSortDesc:F+" sorting_desc",sSortable:F+" sorting",sSortableAsc:F+" sorting_asc_disabled",sSortableDesc:F+" sorting_desc_disabled",sSortableNone:F+" sorting_disabled",sSortJUIAsc:ja+"triangle-1-n",sSortJUIDesc:ja+"triangle-1-s",sSortJUI:ja+"carat-2-n-s",sSortJUIAscAllowed:ja+"carat-1-n",sSortJUIDescAllowed:ja+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+F,sScrollFoot:"dataTables_scrollFoot "+F,
|
||||
sHeaderTH:F,sFooterTH:F,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",Wa(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Wa(a,b),"next","last"]},_numbers:Wa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,e,d,f){var g=a.oClasses,i=
|
||||
a.oLanguage.oPaginate,j,k,l=0,m=function(b,e){var o,r,t,s,u=function(b){Ta(a,b.data.action,true)};o=0;for(r=e.length;o<r;o++){s=e[o];if(h.isArray(s)){t=h("<"+(s.DT_el||"div")+"/>").appendTo(b);m(t,s)}else{k=j="";switch(s){case "ellipsis":b.append('<span class="ellipsis">…</span>');break;case "first":j=i.sFirst;k=s+(d>0?"":" "+g.sPageButtonDisabled);break;case "previous":j=i.sPrevious;k=s+(d>0?"":" "+g.sPageButtonDisabled);break;case "next":j=i.sNext;k=s+(d<f-1?"":" "+g.sPageButtonDisabled);
|
||||
break;case "last":j=i.sLast;k=s+(d<f-1?"":" "+g.sPageButtonDisabled);break;default:j=s+1;k=d===s?g.sPageButtonActive:""}if(j){t=h("<a>",{"class":g.sPageButton+" "+k,"aria-controls":a.sTableId,"data-dt-idx":l,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(j).appendTo(b);Va(t,{action:s},u);l++}}}},o;try{o=h(P.activeElement).data("dt-idx")}catch(r){}m(h(b).empty(),e);o&&h(b).find("[data-dt-idx="+o+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;
|
||||
return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||I(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return I(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,
|
||||
{html:function(a){return I(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ba,""):""},string:function(a){return I(a)?a:"string"===typeof a?a.replace(Ob," "):a}});var Aa=function(a,b,c,e){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),e&&(a=a.replace(e,"")));return 1*a};h.extend(x.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return I(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return I(a)?
|
||||
"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,e){h(a.nTable).on("order.dt.DT",function(d,f,g,h){if(a===f){d=c.idx;b.removeClass(c.sSortingClass+" "+e.sSortAsc+" "+e.sSortDesc).addClass(h[d]=="asc"?e.sSortAsc:h[d]=="desc"?e.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,e){h("<div/>").addClass(e.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(e.sSortIcon+
|
||||
" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(d,f,g,h){if(a===f){d=c.idx;b.removeClass(e.sSortAsc+" "+e.sSortDesc).addClass(h[d]=="asc"?e.sSortAsc:h[d]=="desc"?e.sSortDesc:c.sSortingClass);b.find("span."+e.sSortIcon).removeClass(e.sSortJUIAsc+" "+e.sSortJUIDesc+" "+e.sSortJUI+" "+e.sSortJUIAscAllowed+" "+e.sSortJUIDescAllowed).addClass(h[d]=="asc"?e.sSortJUIAsc:h[d]=="desc"?e.sSortJUIDesc:c.sSortingClassJUI)}})}}});m.render={number:function(a,b,c,e){return{display:function(d){if("number"!==
|
||||
typeof d&&"string"!==typeof d)return d;var f=0>d?"-":"",d=Math.abs(parseFloat(d)),g=parseInt(d,10),d=c?b+(d-g).toFixed(c).substring(2):"";return f+(e||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+d}}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:kb,_fnAjaxParameters:tb,_fnAjaxUpdateDraw:ub,_fnAjaxDataSrc:sa,_fnAddColumn:Fa,_fnColumnOptions:ka,_fnAdjustColumnSizing:X,_fnVisibleToColumnIndex:la,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:Z,_fnColumnTypes:Ha,
|
||||
_fnApplyColumnDefs:ib,_fnHungarianMap:V,_fnCamelToHungarian:H,_fnLanguageCompat:O,_fnBrowserDetect:gb,_fnAddData:J,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:y,_fnSetCellData:Ia,_fnSplitObjNotation:Ka,_fnGetObjectDataFn:W,_fnSetObjectDataFn:Q,_fnGetDataMaster:La,_fnClearTable:oa,_fnDeleteIndex:pa,_fnInvalidate:ca,_fnGetRowElements:na,_fnCreateTr:Ja,_fnBuildHead:jb,
|
||||
_fnDrawHead:ea,_fnDraw:M,_fnReDraw:N,_fnAddOptionsHtml:mb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:ob,_fnFilterComplete:fa,_fnFilterCustom:xb,_fnFilterColumn:wb,_fnFilter:vb,_fnFilterCreateSearch:Qa,_fnEscapeRegex:va,_fnFilterData:yb,_fnFeatureHtmlInfo:rb,_fnUpdateInfo:Bb,_fnInfoMacros:Cb,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:nb,_fnFeatureHtmlPaginate:sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:pb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:qb,
|
||||
_fnScrollDraw:Y,_fnApplyToChildren:G,_fnCalculateColumnWidths:Ga,_fnThrottle:ua,_fnConvertToWidth:Db,_fnScrollingWidthAdjust:Fb,_fnGetWidestNode:Eb,_fnGetMaxLenString:Gb,_fnStringToCss:s,_fnScrollBarWidth:Hb,_fnSortFlatten:T,_fnSort:lb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:R,_fnMap:E,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:w,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:B,
|
||||
_fnRowAttributes:Ma,_fnCalculateEnd:function(){}});h.fn.dataTable=m;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],O):"object"===typeof exports?module.exports=O(require("jquery")):jQuery&&!jQuery.fn.dataTable&&O(jQuery)})(window,document);
|
||||
Reference in New Issue
Block a user