From 481c186e0884ddb645e1ea9e65f6b16b2d5d6eec Mon Sep 17 00:00:00 2001 From: Godwin Date: Thu, 25 Aug 2016 22:29:44 -0700 Subject: [PATCH] Added abaility for admins to add registrations --- app/assets/javascripts/registrations.js | 29 +++ app/assets/stylesheets/_application.scss | 252 +++++++++++++------ app/controllers/conferences_controller.rb | 120 +++++---- app/helpers/application_helper.rb | 86 +++++-- app/views/conferences/admin/_stats.html.haml | 20 +- config/locales/en.yml | 4 +- 6 files changed, 361 insertions(+), 150 deletions(-) diff --git a/app/assets/javascripts/registrations.js b/app/assets/javascripts/registrations.js index 563ceab..a37c302 100644 --- a/app/assets/javascripts/registrations.js +++ b/app/assets/javascripts/registrations.js @@ -52,6 +52,7 @@ request.open('POST', url, true); cells = editRow.getElementsByClassName('cell-editor'); data.append('key', row.getAttribute('data-key')); + data.append('button', 'update'); var changed = false; for (var i = 0; i < cells.length; i++) { if (cells[i].value !== cells[i].getAttribute('data-value')) { @@ -133,4 +134,32 @@ searchControl.addEventListener('keyup', filterTable); searchControl.addEventListener('search', filterTable); + + forEachElement('[data-expands]', function(button) { + button.addEventListener('click', function(event) { + var element = document.getElementById(event.target.getAttribute('data-expands')); + document.body.classList.add('expanded-element'); + element.classList.add('expanded'); + }); + }); + forEachElement('[data-contracts]', function(button) { + button.addEventListener('click', function(event) { + var element = document.getElementById(event.target.getAttribute('data-contracts')); + document.body.classList.remove('expanded-element'); + element.classList.remove('expanded'); + }); + }); + forEachElement('[data-opens-modal]', function(button) { + button.addEventListener('click', function(event) { + var element = document.getElementById(event.target.getAttribute('data-opens-modal')); + document.body.classList.add('modal-open'); + element.classList.add('open'); + }); + }); + forEachElement('[data-closes-modal]', function(element) { + element.addEventListener('click', function(event) { + document.getElementById(event.target.getAttribute('data-closes-modal')).classList.remove('open'); + document.body.classList.remove('modal-open'); + }); + }); })(); diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index 58b17f9..52cfdc8 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -1,3 +1,5 @@ +$bug-list: ("search-element-appearance": ()); + @import "bumbleberry"; @import "settings"; @@ -186,102 +188,114 @@ table, .table { tr[data-key] { cursor: cell; - - &:hover { + + &.editable:hover { background-color: lighten($colour-2, 33%); } - &.editable { - + .editor { - display: none; - background-color: lighten($colour-1, 50%); - - td { - position: relative; - vertical-align: top; - background: inherit; - cursor: default; - opacity: 0.5; - - &.has-editor { - opacity: 1; - - @include after { - content: ''; - position: absolute; - top: 100%; - right: 0; - left: 0; - height: 0.25em; - background-color: rgba($black, 0.125); - } - } + + .editor { + display: none; + background-color: lighten($colour-1, 50%); - .cell-editor { - top: 0; + td { + opacity: 0.5; + + &.has-editor { + opacity: 1; + + @include after { + content: ''; + position: absolute; + top: 100%; right: 0; - bottom: 0; left: 0; - padding: inherit; - font: inherit; - margin: inherit; - background: inherit; - border: 0; - min-height: 0; - width: 100% !important; - border-radius: 0; - line-height: inherit; - overflow: hidden; - box-shadow: none; - text-align: inherit; - - &[type=number]::-webkit-inner-spin-button, - &[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - } + height: 0.25em; + background-color: rgba($black, 0.125); } + } - select.cell-editor { - -webkit-appearance: none; - -moz-appearance: none; - -ms-appearance: none; - appearance: none; - cursor: pointer; + .cell-editor { + &[type=number]::-webkit-inner-spin-button, + &[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; } + } - &.date .cell-editor { - text-align-last: right; - } + select.cell-editor { + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + appearance: none; + cursor: pointer; + } + + &.date .cell-editor { + text-align-last: right; } } + } - &.editing { - display: none; + + .editor, &.always-edit { + td { + position: relative; + vertical-align: top; + background: inherit; + cursor: default; + + .cell-editor { + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: inherit; + font: inherit; + margin: inherit; + background: inherit; + border: 0; + min-height: 0; + width: 100% !important; + border-radius: 0; + line-height: inherit; + overflow: hidden; + box-shadow: none; + text-align: inherit; + } + } + } + + &.always-edit td .cell-editor { + position: absolute; + } - + .editor { - display: table-row; + &.editing { + display: none; - .cell-editor { - position: absolute; - } + + .editor { + display: table-row; + + .cell-editor { + position: absolute; } } } + } - /*td[name] { - position: relative; - cursor: text; + &.always-editing { + tr { + cursor: default; &:hover { - background-color: lighten($colour-2, 25%); + background-color: transparent; } + } - input, textarea, select { - } + .cell-editor { + position: absolute; } - &[data-editing="1"] { - }*/ + td.text { + height: 5em; + } } tr.editable, tr.editor { @@ -320,7 +334,97 @@ table, .table { .table-scroller { overflow: auto; - background-color: $white; + background-color: #F8F8F8; + @include _(box-shadow, inset 0 0 10em 0 rgba(0,0,0,0.125)); + + table { + background-color: $white; + margin: 0 0 8.5em; + } + + body.expanded-element .expanded & { + overflow: visible; + } +} + +.goes-fullscreen { + [data-contracts] { + display: none; + } +} + +body.modal-open { + overflow: hidden; +} + +body.expanded-element { + overflow: hidden; + + .goes-fullscreen.expanded { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1000; + background-color: $white; + overflow: auto; + padding: 0 1em; + + [data-expands] { + display: none; + } + + [data-contracts] { + display: block; + } + } +} + +#main .columns .modal-edit { + display: none; + position: fixed; + top: 0; + right: 0; + left: 0; + bottom: 0; + z-index: 1001; + margin: 0; + background-color: rgba($black, 0.5); + + &.open { + display: block; + } + + .modal-edit-overlay { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + cursor: pointer; + } + + table { + margin: 0; + } + + .modal-edit-content { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + max-width: 40em; + margin: auto; + overflow: auto; + z-index: 1002; + background-color: #F8F8F8; + } + + table { + background-color: $white; + } } .table { @@ -1111,6 +1215,10 @@ fieldset { button, .button { width: 100%; text-align: center; + + + button, + .button { + margin-left: 0.75em; + } } } diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb index fad8780..a7cfc92 100644 --- a/app/controllers/conferences_controller.rb +++ b/app/controllers/conferences_controller.rb @@ -508,6 +508,7 @@ class ConferencesController < ApplicationController column_types: { name: :bold, date: :datetime, + email: :email, companion_email: :email, arrival: [:date, :day], departure: [:date, :day], @@ -532,7 +533,7 @@ class ConferencesController < ApplicationController bike: 'forms.labels.generic.bike', food: 'forms.labels.generic.food', companion: 'articles.conference_registration.terms.companion', - companion_email: 'forms.labels.generic.email', + companion_email: 'articles.conference_registration.terms.companion_email', allergies: 'forms.labels.generic.allergies', registration_fees_paid: 'articles.conference_registration.headings.fees_paid', other: 'articles.conference_registration.headings.other', @@ -645,7 +646,7 @@ class ConferencesController < ApplicationController preferred_language: I18n.backend.enabled_locales.map { |l| [ (view_context.language_name l), l ] }, - is_attending: yes_no, + is_attending: [yes_no.first], can_provide_housing: yes_no, first_day: view_context.conference_days_options_list(:before), last_day: view_context.conference_days_options_list(:after) @@ -782,59 +783,80 @@ class ConferencesController < ApplicationController case params[:admin_step] when 'stats' - registration = ConferenceRegistration.where( - id: params[:key].to_i, - conference_id: @this_conference.id - ).limit(1).first - user_changed = false - params.each do | key, value | - case key.to_sym - when :city - registration.city = value.present? ? view_context.location(Geocoder.search(value, language: @this_conference.locale).first, @this_conference.locale) : nil - when :housing, :bike, :food, :allergies, :other - registration.send("#{key.to_s}=", value) - when :registration_fees_paid - registration.registration_fees_paid = value.to_i - when :can_provide_housing - registration.send("#{key.to_s}=", value.present?) - when :arrival, :departure - registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil) - when :companion_email - registration.housing_data ||= {} - registration.housing_data['companions'] = [value] - when :preferred_language - registration.user.locale = value - user_changed = true - when :is_attending - registration.is_attending = value.present? ? 'y' : 'n' - when :address, :phone, :first_day, :last_day, :notes - registration.housing_data ||= {} - registration.housing_data[key.to_s] = value + if params[:button] == 'save' || params[:button] == 'update' + if params[:button] == 'save' + return do_404 unless params[:email].present? && params[:name].present? + + user = User.find_by_email(params[:email]) || User.create(email: params[:email]) + user.firstname = params[:name] + user.save! + registration = ConferenceRegistration.new( + conference: @this_conference, + user_id: user.id, + steps_completed: [] + ) else - if key.start_with?('language_') - l = key.split('_').last - if User.AVAILABLE_LANGUAGES.include? l.to_sym - registration.user.languages ||= [] - if value.present? - registration.user.languages |= [l] - else - registration.user.languages -= [l] + registration = ConferenceRegistration.where( + id: params[:key].to_i, + conference_id: @this_conference.id + ).limit(1).first + end + + user_changed = false + params.each do | key, value | + case key.to_sym + when :city + registration.city = value.present? ? view_context.location(Geocoder.search(value, language: @this_conference.locale).first, @this_conference.locale) : nil + when :housing, :bike, :food, :allergies, :other + registration.send("#{key.to_s}=", value) + when :registration_fees_paid + registration.registration_fees_paid = value.to_i + when :can_provide_housing + registration.send("#{key.to_s}=", value.present?) + when :arrival, :departure + registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil) + when :companion_email + registration.housing_data ||= {} + registration.housing_data['companions'] = [value] + when :preferred_language + registration.user.locale = value + user_changed = true + when :is_attending + registration.is_attending = value.present? ? 'y' : 'n' + when :address, :phone, :first_day, :last_day, :notes + registration.housing_data ||= {} + registration.housing_data[key.to_s] = value + else + if key.start_with?('language_') + l = key.split('_').last + if User.AVAILABLE_LANGUAGES.include? l.to_sym + registration.user.languages ||= [] + if value.present? + registration.user.languages |= [l] + else + registration.user.languages -= [l] + end + user_changed = true end - user_changed = true + elsif ConferenceRegistration.all_spaces.include? key.to_sym + registration.housing_data ||= {} + registration.housing_data['space'] ||= {} + registration.housing_data['space'][key.to_s] = value end - elsif ConferenceRegistration.all_spaces.include? key.to_sym - registration.housing_data ||= {} - registration.housing_data['space'] ||= {} - registration.housing_data['space'][key.to_s] = value end end + registration.user.save! if user_changed + registration.save! + + if params[:button] == 'save' + return redirect_to register_step_path(@this_conference.slug, :administration) + end + + get_stats(true, params[:key].to_i) + options = view_context.registrations_table_options + options[:html] = true + return render html: view_context.excel_rows(@excel_data, {}, options) end - registration.user.save! if user_changed - registration.save! - get_stats(true, params[:key].to_i) - options = view_context.registrations_table_options - options[:html] = true - return render html: view_context.excel_rows(@excel_data, {}, options) when 'edit' case params[:button] when 'save' diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a668d26..f8e4cfc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1538,6 +1538,27 @@ module ApplicationHelper end end + def html_edit_table(excel_data, options = {}) + attributes = { class: options[:class], id: options[:id] } + attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? + + content_tag(:table, attributes) do + (content_tag(:tbody) do + rows = '' + excel_data[:columns].each do |column| + if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column) + rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do + attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } + columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') + + edit_column(nil, column, nil, attributes, excel_data, options) + end + end + end + rows.html_safe + end) + end + end + def html_table(excel_data, options = {}) options[:html] = true attributes = { class: options[:class], id: options[:id] } @@ -1672,38 +1693,46 @@ module ApplicationHelper end if (options[:column_names] || []).include? column - attributes[:class] << 'has-editor' - raw_value = row[:raw_values][column] || value - - if options[:html] && row[:html_values].present? && row[:html_values][column].present? - value = row[:html_values][column] - end - - editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } } - - # create the control but add the original value to set the width and height - editor_value = content_tag(:div, value, class: 'value') - if (options[:column_options] || {})[column].present? - value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe - elsif data[:column_types][column] == :text - editor_attributes[:name] = column - value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe - else - editor_attributes[:name] = column - editor_attributes[:value] = raw_value - type = data[:column_types][column] || :unknown - editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text - value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe - end + columns += edit_column(row, column, value, attributes, data, options) + else + columns += content_tag(:td, value, attributes) end - columns += content_tag(:td, value, attributes) end end pad_columns(columns, padding) end + def edit_column(row, column, value, attributes, data, options) + attributes[:class] << 'has-editor' + raw_value = row.present? ? (row[:raw_values][column] || value) : nil + + if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present? + value = row[:html_values][column] + end + + editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } } + + # create the control but add the original value to set the width and height + editor_value = content_tag(:div, value, class: 'value') + if (options[:column_options] || {})[column].present? + value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe + elsif data[:column_types][column] == :text + editor_attributes[:name] = column + value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe + else + editor_attributes[:name] = column + editor_attributes[:value] = raw_value + editor_attributes[:required] = :required if (options[:required_columns] || []).include? column + type = data[:column_types][column] || :unknown + editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text + value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe + end + + return content_tag(:td, value, attributes) + end + def excel_sub_tables(row, data, padding = {}, options = {}) rows = '' @@ -1747,6 +1776,15 @@ module ApplicationHelper rows.html_safe end + def registrations_edit_table_options + options = registrations_table_options + options[:id] = 'create-table' + options[:class] << 'always-editing' + options[:column_names] += [:name, :email] + options[:required_columns] = [:name, :email] + return options + end + def registrations_table_options { id: 'search-table', diff --git a/app/views/conferences/admin/_stats.html.haml b/app/views/conferences/admin/_stats.html.haml index 7643dbe..15acc15 100644 --- a/app/views/conferences/admin/_stats.html.haml +++ b/app/views/conferences/admin/_stats.html.haml @@ -19,7 +19,19 @@ .actions = link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :stats, :format => :xlsx), class: [:button, :download] = link_to (_'links.download.Organizations_Excel'), administration_step_path(@this_conference.slug, :organizations, :format => :xlsx), class: [:button, :download, :subdued] -%h4 Registrations -= searchfield :search, nil, big: true -.table-scroller - = html_table @excel_data, registrations_table_options +%h4=_'articles.admin.stats.headings.Registrations' +.goes-fullscreen#registrations-table + .flex-column + = searchfield :search, nil, big: true, stretch: true + %a.button{data: { expands: 'registrations-table' }}='expand' + %a.button.delete{data: { contracts: 'registrations-table' }}='close' + %a.button.modify{data: { 'opens-modal': 'new-registration' }}='+' + .table-scroller + = html_table @excel_data, registrations_table_options + = form_tag administration_update_path(@this_conference.slug, :stats), id: 'new-registration', class: 'modal-edit' do + .modal-edit-overlay{data: { 'closes-modal': 'new-registration' }} + .modal-edit-content + =html_edit_table @excel_data, registrations_edit_table_options + .actions + %a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' + = button_tag :save, value: :save, class: :modify diff --git a/config/locales/en.yml b/config/locales/en.yml index fb0553c..9c6437b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -882,7 +882,8 @@ en: vegan: Vegans registrations: Number of registrations completed_registrations: Number of registrations - incomplete_registrations: Incomplete registrations + completed_registrations: Number of registrations + Registrations: Registrations meals: description: On this page you can schedule the meals that you will be serving. no_locations_warning: Before you can add meals, you must first add locations. @@ -1116,6 +1117,7 @@ en: preregistered: Preregistered registered: Registered companion: Companion + companion_email: Companion Email Preferred_Languages: Preferred Language is_attending: Attending? about_bikebike: