diff --git a/.rubocop.yml b/.rubocop.yml index 512bef00..bbe91768 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -95,7 +95,7 @@ Metrics/BlockNesting: Max: 4 Metrics/ClassLength: - Max: 550 + Max: 560 # avoid redundunt curly braces when it is obvious that hash is used Style/BracesAroundHashParameters: diff --git a/app/assets/stylesheets/osem-dashboard.css.scss b/app/assets/stylesheets/osem-dashboard.css.scss index 394fd764..5dc69879 100644 --- a/app/assets/stylesheets/osem-dashboard.css.scss +++ b/app/assets/stylesheets/osem-dashboard.css.scss @@ -57,3 +57,7 @@ display: inline-block; padding: 0px 10px 0px 0px; } + +.margin-event-table{ + margin-top: 40px !important; +} diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index f615aa5e..e08ddde7 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -21,6 +21,9 @@ module Admin @difficulty_levels = @program.difficulty_levels @machine_states = @events.state_machine.states.map @event_types = @program.event_types + @tracks_distribution_confirmed = @conference.tracks_distribution(:confirmed) + @event_distribution = @conference.event_distribution + @scheduled_event_distribution = @conference.scheduled_event_distribution @mystates = [] @mytypes = [] diff --git a/app/controllers/admin/registrations_controller.rb b/app/controllers/admin/registrations_controller.rb index 9c60ca14..1ab957f9 100644 --- a/app/controllers/admin/registrations_controller.rb +++ b/app/controllers/admin/registrations_controller.rb @@ -9,6 +9,9 @@ module Admin @pdf_filename = "#{@conference.title}.pdf" @registrations = @conference.registrations.includes(:user).order('registrations.created_at ASC') @attended = @conference.registrations.where('attended = ?', true).count + + @registration_distribution = @conference.registration_distribution + @affiliation_distribution = @conference.affiliation_distribution end def edit; end diff --git a/app/models/conference.rb b/app/models/conference.rb index f0b73047..bf697909 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -316,6 +316,67 @@ class Conference < ActiveRecord::Base Conference.calculate_event_distribution_hash(program.events.select(:state).group(:state).count) end + ## + # Returns a hash with scheduled vs unscheduled events + # { "Scheduled" => { value: number of confirmed and scheduled events, color: color }, + # "Unscheduled" => { value: number of confirmed and unscheduled events, color: color } + # + # ====Returns + # * +hash+ -> hash + def scheduled_event_distribution + confirmed_events = program.events.where(state: 'confirmed') + scheduled_value = { 'value' => confirmed_events.where.not(start_time: nil).count, 'color' => 'green' } + unscheduled_value = { 'value' => confirmed_events.where(start_time: nil).count, 'color' => 'red' } + { 'Scheduled' => scheduled_value, 'Unscheduled' => unscheduled_value } + end + + ## + # Returns a hash with Registration attended vs. Registration not attended + # { "Attended" => { value: number of registration attended, color: color }, + # "Not attended" => { value: number of registration not attended, color: color } + # + # ====Returns + # * +hash+ -> hash + def registration_distribution + reg = registrations.includes(:user) + attended_value = { 'value' => reg.where(attended: true).count, 'color' => 'magenta' } + not_attended_value = { 'value' => reg.where.not(attended: true).count, 'color' => 'blue' } + { 'Attended' => attended_value, 'Not attended' => not_attended_value } + end + + ## + # Returns a hash with affiliation => + # {value: count of registration whose user has that affilation, color: color} + # In case that the affiliation is blank, it groups them in None and + # if the number of persons that have an affiliation are less than the 2% of + # the total number of registered people, they are grouped in others. + # + # ====Returns + # * +hash+ -> hash + def affiliation_distribution + counted_affiliations = registrations.joins(:user).group(:affiliation).count + result = {} + i=1 + others = 0 + none = 0 + counted_affiliations.each do |key, value| + if value < 0.02 * registrations.length + others += value + elsif key.blank? + none += value + else + result[key.capitalize] = { 'value' => value, 'color' => next_color(i) } + i += 1 + end + end + if others > 0 + result['Others'] = { 'value' => others, 'color' => next_color(i) } + i += 1 + end + result['None'] = { 'value' => none, 'color' => next_color(i) } if none > 0 + result + end + ## # Returns a hash with user distribution => {value: count of user state, color: color} # active: signed in during the last 3 months @@ -536,6 +597,20 @@ class Conference < ActiveRecord::Base private + # Returns a different html colour for every i and consecutive colors are + # clearly different. + def next_color(i) + '#' + next_color_component(:r, i) + next_color_component(:g, i) + next_color_component(:b, i) + end + + # Auxiliar function which is used in next_color and returns each component of + # the color. We make use of big prime numbers to avoid repetition and to make + # consecutive colors clearly different. + def next_color_component(component, i) + big_prime_numbers = {r: 113, g: 67, b: 151} + ((i*big_prime_numbers[component])%239 + 16).to_s(16) + end + after_create do self.create_contact self.create_program diff --git a/app/views/admin/events/index.html.haml b/app/views/admin/events/index.html.haml index f5aeea9c..83f44eb3 100644 --- a/app/views/admin/events/index.html.haml +++ b/app/views/admin/events/index.html.haml @@ -7,161 +7,169 @@ = "(#{@events.length})" %p.text-muted All the submissions of your speakers +.row + .col-md-4 + = render partial: 'admin/conference/doughnut_chart', locals: {title: 'Events state', data: @event_distribution} + .col-md-4 + = render partial: 'admin/conference/doughnut_chart', locals: {title: 'Confirmed events scheduled', data: @scheduled_event_distribution} + .col-md-4 + = render partial: 'admin/conference/doughnut_chart', locals: {title: 'Tracks of confirmed events', data: @tracks_distribution_confirmed} .row .col-md-12 - %table.table.table-striped.table-bordered.table-hover.datatable - %thead - %th - %b ID - %th - %b Title - - if @program.rating_enabled? + %div.margin-event-table + %table.table.table-striped.table-bordered.table-hover.datatable + %thead %th - %b Rating - %th - %b Submitter - %th - %b Speaker - -if @program.languages.present? + %b ID %th - %b Language - %th - %b Requires Registration - %th - %b Highlight - %th - %b Type - %th - %b Track - %th - %b Difficulty - %th - %b State - - @events.each do |event| - %tr - %td - = event.id - %td - =link_to event.title, admin_conference_program_event_path(@conference.short_title, event) - + %b Title - if @program.rating_enabled? - %td.col-md-1{'data-order' => "#{event.average_rating}"} - - if event.average_rating.to_f > 0 - #{event.average_rating}/#{@program.rating} - %br - #{pluralize(event.voters.length, 'voter')} - %br - - @program.rating.times do |counter| - - if event.average_rating.to_f.round == counter+1 - = label_tag "label_rating", "", :class => "avgrating", :avgrate => true - = javascript_tag "$('label[avgrate=true]').prevAll().andSelf().addClass('bright');" - - else - = label_tag "label_rating", "", :class => "avgrating" - %br - - voted = event.voted?(event, current_user) - - if voted - %span.label.label-success - Your rating: #{voted.rating} - - else - %span.label.label-danger - Not rated - - else - 0/#{@program.rating} - %br - - - if event.submitter && event.submitter.registrations && event.submitter.registrations.count < 1 - - bgcolor="#F7819F" - - else - - bgcolor="" - %td{:style=>"background-color: #{bgcolor}"} - - if !event.submitter.nil? - =link_to event.submitter.name, admin_user_path(event.submitter) - - if event.submitter.registrations.count < 1 - (Unregistered!) - - else - Unknown submitter - %td - - if speaker = event.speakers.first - = link_to speaker.name, admin_user_path(speaker) - - else - Unknown speaker - + %th + %b Rating + %th + %b Submitter + %th + %b Speaker -if @program.languages.present? + %th + %b Language + %th + %b Requires Registration + %th + %b Highlight + %th + %b Type + %th + %b Track + %th + %b Difficulty + %th + %b State + - @events.each do |event| + %tr %td - = event.language + = event.id + %td + =link_to event.title, admin_conference_program_event_path(@conference.short_title, event) - %td.text-center{'data-order' => "#{event.require_registration}"} - = check_box_tag @conference.short_title, event.id, event.require_registration, - method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{event.id}?event[require_registration]=", - class: 'switch-checkbox', data: { size: 'small', - off_color: 'warning', - on_text: 'Yes', - off_text: 'No' } - - if event.require_registration - %br - = link_to registered_text(event), registrations_admin_conference_program_event_path(@conference.short_title, event), class: 'btn btn-xs btn-default' - - %td.text-center{'data-order' => "#{event.is_highlight}"} - = check_box_tag @conference.short_title, event.id, event.is_highlight, - method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{event.id}?event[is_highlight]=", - class: 'switch-checkbox', data: { size: 'small', - off_color: 'warning', - on_text: 'Yes', - off_text: 'No' } - - %td - .btn-group - %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} - - if event.event_type.nil? - Event Type + - if @program.rating_enabled? + %td.col-md-1{'data-order' => "#{event.average_rating}"} + - if event.average_rating.to_f > 0 + #{event.average_rating}/#{@program.rating} + %br + #{pluralize(event.voters.length, 'voter')} + %br + - @program.rating.times do |counter| + - if event.average_rating.to_f.round == counter+1 + = label_tag "label_rating", "", :class => "avgrating", :avgrate => true + = javascript_tag "$('label[avgrate=true]').prevAll().andSelf().addClass('bright');" + - else + = label_tag "label_rating", "", :class => "avgrating" + %br + - voted = event.voted?(event, current_user) + - if voted + %span.label.label-success + Your rating: #{voted.rating} + - else + %span.label.label-danger + Not rated - else - = event.event_type.title - %span.caret - %ul.dropdown-menu - - @event_types.each do |type| - %li= link_to type.title, - admin_conference_program_event_path(@conference.short_title, - event, - event: { event_type_id: type.id }), - method: :patch - %td - .btn-group - %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} - - if event.track.nil? - Track - - else - = event.track.name - %span.caret - %ul.dropdown-menu - - @tracks.each do |track| - %li= link_to track.name, - admin_conference_program_event_path(@conference.short_title, - event, - event: { track_id: track.id }), - method: :patch - %td - .btn-group - %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} - - if event.difficulty_level.nil? - Difficulty - - else - = event.difficulty_level.title - %span.caret - %ul.dropdown-menu - - @difficulty_levels.each do |difficulty_level| - %li= link_to difficulty_level.title, - admin_conference_program_event_path(@conference.short_title, - event, - event: { difficulty_level_id: difficulty_level.id }), - method: :patch + 0/#{@program.rating} + %br - %td - - if event.state == "withdrawn" - Withdrawn + - if event.submitter && event.submitter.registrations && event.submitter.registrations.count < 1 + - bgcolor="#F7819F" - else + - bgcolor="" + %td{:style=>"background-color: #{bgcolor}"} + - if !event.submitter.nil? + =link_to event.submitter.name, admin_user_path(event.submitter) + - if event.submitter.registrations.count < 1 + (Unregistered!) + - else + Unknown submitter + %td + - if speaker = event.speakers.first + = link_to speaker.name, admin_user_path(speaker) + - else + Unknown speaker + + -if @program.languages.present? + %td + = event.language + + %td.text-center{'data-order' => "#{event.require_registration}"} + = check_box_tag @conference.short_title, event.id, event.require_registration, + method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{event.id}?event[require_registration]=", + class: 'switch-checkbox', data: { size: 'small', + off_color: 'warning', + on_text: 'Yes', + off_text: 'No' } + - if event.require_registration + %br + = link_to registered_text(event), registrations_admin_conference_program_event_path(@conference.short_title, event), class: 'btn btn-xs btn-default' + + %td.text-center{'data-order' => "#{event.is_highlight}"} + = check_box_tag @conference.short_title, event.id, event.is_highlight, + method: :patch, url: "/admin/conference/#{@conference.short_title}/program/events/#{event.id}?event[is_highlight]=", + class: 'switch-checkbox', data: { size: 'small', + off_color: 'warning', + on_text: 'Yes', + off_text: 'No' } + + %td .btn-group %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} - = event.state.humanize + - if event.event_type.nil? + Event Type + - else + = event.event_type.title %span.caret - %ul.dropdown-menu{:role=>"menu"} - = render 'change_state_dropdown', event: event + %ul.dropdown-menu + - @event_types.each do |type| + %li= link_to type.title, + admin_conference_program_event_path(@conference.short_title, + event, + event: { event_type_id: type.id }), + method: :patch + %td + .btn-group + %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} + - if event.track.nil? + Track + - else + = event.track.name + %span.caret + %ul.dropdown-menu + - @tracks.each do |track| + %li= link_to track.name, + admin_conference_program_event_path(@conference.short_title, + event, + event: { track_id: track.id }), + method: :patch + %td + .btn-group + %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} + - if event.difficulty_level.nil? + Difficulty + - else + = event.difficulty_level.title + %span.caret + %ul.dropdown-menu + - @difficulty_levels.each do |difficulty_level| + %li= link_to difficulty_level.title, + admin_conference_program_event_path(@conference.short_title, + event, + event: { difficulty_level_id: difficulty_level.id }), + method: :patch + + %td + - if event.state == "withdrawn" + Withdrawn + - else + .btn-group + %button{:type=>"button", :class=>"btn btn-link dropdown-toggle", "data-toggle"=>"dropdown"} + = event.state.humanize + %span.caret + %ul.dropdown-menu{:role=>"menu"} + = render 'change_state_dropdown', event: event diff --git a/app/views/admin/registrations/index.html.haml b/app/views/admin/registrations/index.html.haml index 3e581914..be03da65 100644 --- a/app/views/admin/registrations/index.html.haml +++ b/app/views/admin/registrations/index.html.haml @@ -11,57 +11,65 @@ = link_to 'Export XLS', {format: :xlsx}, class: 'btn btn-success' %p.text-muted All the people who registered to your event - %table.table.table-hover.datatable#registrations - %thead - %tr - %th ID# - %th Name - %th E-Mail - %th Arrival - %th Departure - - if @conference.questions.any? - %th Questions - %th Actions - %tbody - - @registrations.each_with_index do |registration, index| + .col-md-4 + = render partial: 'admin/conference/doughnut_chart', locals: {title: 'Affiliation', data: @affiliation_distribution} + - unless @conference.pending? + .col-md-4 + = render partial: 'admin/conference/doughnut_chart', locals: {title: 'Attended registrations', data: @registration_distribution} +.row + .col-md-12 + %div.margin-event-table + %table.table.table-hover.datatable#registrations + %thead %tr - %td - = registration.id - %td - = registration.name.present? ? registration.name : registration.username - %br - - registration.user.roles.where(resource: @conference).each do |role| - %span.label.label-info - = role.name.titleize - %td - = registration.email - %td - - if registration.arrival - = registration.arrival.strftime('%d %b %H:%M') - - else - n/a - %td - - if registration.departure - = registration.departure.strftime('%d %b %H:%M') - - else - n/a - -if @conference.questions.any? + %th ID# + %th Name + %th E-Mail + %th Arrival + %th Departure + - if @conference.questions.any? + %th Questions + %th Actions + %tbody + - @registrations.each_with_index do |registration, index| + %tr %td - = link_to 'Questions','#', class: 'btn btn-success question-btn', 'data-id' => index, 'data-name' => registration.name - %td - = check_box_tag "#{@conference.short_title}_#{registration.id}", registration.id, registration.attended, - class: 'switch-checkbox', method: :patch, - url: toggle_attendance_admin_conference_registration_path(@conference.short_title, id: registration.id)+"?attended=", - data: { size: 'small', - on_color: 'success', - off_color: 'warning', - on_text: 'Present', - off_text: 'Absent' } - .btn-group - = link_to 'Edit', edit_admin_conference_registration_path(@conference.short_title, id: registration), - method: :get, class: 'btn btn-primary' - = link_to 'Delete', admin_conference_registration_path(@conference.short_title, registration), - method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete the Registration for #{registration.name}?" } + = registration.id + %td + = registration.name.present? ? registration.name : registration.username + %br + - registration.user.roles.where(resource: @conference).each do |role| + %span.label.label-info + = role.name.titleize + %td + = registration.email + %td + - if registration.arrival + = registration.arrival.strftime('%d %b %H:%M') + - else + n/a + %td + - if registration.departure + = registration.departure.strftime('%d %b %H:%M') + - else + n/a + -if @conference.questions.any? + %td + = link_to 'Questions','#', class: 'btn btn-success question-btn', 'data-id' => index, 'data-name' => registration.name + %td + = check_box_tag "#{@conference.short_title}_#{registration.id}", registration.id, registration.attended, + class: 'switch-checkbox', method: :patch, + url: toggle_attendance_admin_conference_registration_path(@conference.short_title, id: registration.id)+"?attended=", + data: { size: 'small', + on_color: 'success', + off_color: 'warning', + on_text: 'Present', + off_text: 'Absent' } + .btn-group + = link_to 'Edit', edit_admin_conference_registration_path(@conference.short_title, id: registration), + method: :get, class: 'btn btn-primary' + = link_to 'Delete', admin_conference_registration_path(@conference.short_title, registration), + method: :delete, class: 'btn btn-danger', data: { confirm: "Do you really want to delete the Registration for #{registration.name}?" } - @registrations.each_with_index do |registration, index| .questions{class: "question#{index}", style: 'display:none;'} = render partial: 'questions', locals: { registration: registration } diff --git a/db/schema.rb b/db/schema.rb index b0626d92..aa8021c8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -343,9 +343,13 @@ ActiveRecord::Schema.define(version: 20160624151257) do t.boolean "include_registrations" t.boolean "include_sponsors" t.boolean "include_lodgings" + t.string "banner_photo_file_name" + t.string "banner_photo_content_type" + t.integer "banner_photo_file_size" + t.datetime "banner_photo_updated_at" t.datetime "created_at" t.datetime "updated_at" - t.boolean "include_cfp", default: false + t.boolean "include_cfp", default: false end create_table "sponsors", force: :cascade do |t|