Browse Source

Added abaility for admins to add registrations

development
Godwin 8 years ago
parent
commit
481c186e08
  1. 29
      app/assets/javascripts/registrations.js
  2. 252
      app/assets/stylesheets/_application.scss
  3. 120
      app/controllers/conferences_controller.rb
  4. 86
      app/helpers/application_helper.rb
  5. 20
      app/views/conferences/admin/_stats.html.haml
  6. 4
      config/locales/en.yml

29
app/assets/javascripts/registrations.js

@ -52,6 +52,7 @@
request.open('POST', url, true); request.open('POST', url, true);
cells = editRow.getElementsByClassName('cell-editor'); cells = editRow.getElementsByClassName('cell-editor');
data.append('key', row.getAttribute('data-key')); data.append('key', row.getAttribute('data-key'));
data.append('button', 'update');
var changed = false; var changed = false;
for (var i = 0; i < cells.length; i++) { for (var i = 0; i < cells.length; i++) {
if (cells[i].value !== cells[i].getAttribute('data-value')) { if (cells[i].value !== cells[i].getAttribute('data-value')) {
@ -133,4 +134,32 @@
searchControl.addEventListener('keyup', filterTable); searchControl.addEventListener('keyup', filterTable);
searchControl.addEventListener('search', 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');
});
});
})(); })();

252
app/assets/stylesheets/_application.scss

@ -1,3 +1,5 @@
$bug-list: ("search-element-appearance": ());
@import "bumbleberry"; @import "bumbleberry";
@import "settings"; @import "settings";
@ -186,102 +188,114 @@ table, .table {
tr[data-key] { tr[data-key] {
cursor: cell; cursor: cell;
&:hover { &.editable:hover {
background-color: lighten($colour-2, 33%); background-color: lighten($colour-2, 33%);
} }
&.editable { + .editor {
+ .editor { display: none;
display: none; background-color: lighten($colour-1, 50%);
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);
}
}
.cell-editor { td {
top: 0; opacity: 0.5;
&.has-editor {
opacity: 1;
@include after {
content: '';
position: absolute;
top: 100%;
right: 0; right: 0;
bottom: 0;
left: 0; left: 0;
padding: inherit; height: 0.25em;
font: inherit; background-color: rgba($black, 0.125);
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;
}
} }
}
select.cell-editor { .cell-editor {
-webkit-appearance: none; &[type=number]::-webkit-inner-spin-button,
-moz-appearance: none; &[type=number]::-webkit-outer-spin-button {
-ms-appearance: none; -webkit-appearance: none;
appearance: none;
cursor: pointer;
} }
}
&.date .cell-editor { select.cell-editor {
text-align-last: right; -webkit-appearance: none;
} -moz-appearance: none;
-ms-appearance: none;
appearance: none;
cursor: pointer;
}
&.date .cell-editor {
text-align-last: right;
} }
} }
}
&.editing { + .editor, &.always-edit {
display: none; 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 { &.editing {
display: table-row; display: none;
.cell-editor { + .editor {
position: absolute; display: table-row;
}
.cell-editor {
position: absolute;
} }
} }
} }
}
/*td[name] { &.always-editing {
position: relative; tr {
cursor: text; cursor: default;
&:hover { &: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 { tr.editable, tr.editor {
@ -320,7 +334,97 @@ table, .table {
.table-scroller { .table-scroller {
overflow: auto; 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 { .table {
@ -1111,6 +1215,10 @@ fieldset {
button, .button { button, .button {
width: 100%; width: 100%;
text-align: center; text-align: center;
+ button, + .button {
margin-left: 0.75em;
}
} }
} }

120
app/controllers/conferences_controller.rb

@ -508,6 +508,7 @@ class ConferencesController < ApplicationController
column_types: { column_types: {
name: :bold, name: :bold,
date: :datetime, date: :datetime,
email: :email,
companion_email: :email, companion_email: :email,
arrival: [:date, :day], arrival: [:date, :day],
departure: [:date, :day], departure: [:date, :day],
@ -532,7 +533,7 @@ class ConferencesController < ApplicationController
bike: 'forms.labels.generic.bike', bike: 'forms.labels.generic.bike',
food: 'forms.labels.generic.food', food: 'forms.labels.generic.food',
companion: 'articles.conference_registration.terms.companion', 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', allergies: 'forms.labels.generic.allergies',
registration_fees_paid: 'articles.conference_registration.headings.fees_paid', registration_fees_paid: 'articles.conference_registration.headings.fees_paid',
other: 'articles.conference_registration.headings.other', other: 'articles.conference_registration.headings.other',
@ -645,7 +646,7 @@ class ConferencesController < ApplicationController
preferred_language: I18n.backend.enabled_locales.map { |l| [ preferred_language: I18n.backend.enabled_locales.map { |l| [
(view_context.language_name l), l (view_context.language_name l), l
] }, ] },
is_attending: yes_no, is_attending: [yes_no.first],
can_provide_housing: yes_no, can_provide_housing: yes_no,
first_day: view_context.conference_days_options_list(:before), first_day: view_context.conference_days_options_list(:before),
last_day: view_context.conference_days_options_list(:after) last_day: view_context.conference_days_options_list(:after)
@ -782,59 +783,80 @@ class ConferencesController < ApplicationController
case params[:admin_step] case params[:admin_step]
when 'stats' when 'stats'
registration = ConferenceRegistration.where( if params[:button] == 'save' || params[:button] == 'update'
id: params[:key].to_i, if params[:button] == 'save'
conference_id: @this_conference.id return do_404 unless params[:email].present? && params[:name].present?
).limit(1).first
user_changed = false user = User.find_by_email(params[:email]) || User.create(email: params[:email])
params.each do | key, value | user.firstname = params[:name]
case key.to_sym user.save!
when :city registration = ConferenceRegistration.new(
registration.city = value.present? ? view_context.location(Geocoder.search(value, language: @this_conference.locale).first, @this_conference.locale) : nil conference: @this_conference,
when :housing, :bike, :food, :allergies, :other user_id: user.id,
registration.send("#{key.to_s}=", value) steps_completed: []
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 else
if key.start_with?('language_') registration = ConferenceRegistration.where(
l = key.split('_').last id: params[:key].to_i,
if User.AVAILABLE_LANGUAGES.include? l.to_sym conference_id: @this_conference.id
registration.user.languages ||= [] ).limit(1).first
if value.present? end
registration.user.languages |= [l]
else user_changed = false
registration.user.languages -= [l] 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 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 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
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 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' when 'edit'
case params[:button] case params[:button]
when 'save' when 'save'

86
app/helpers/application_helper.rb

@ -1538,6 +1538,27 @@ module ApplicationHelper
end end
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 = {}) def html_table(excel_data, options = {})
options[:html] = true options[:html] = true
attributes = { class: options[:class], id: options[:id] } attributes = { class: options[:class], id: options[:id] }
@ -1672,38 +1693,46 @@ module ApplicationHelper
end end
if (options[:column_names] || []).include? column if (options[:column_names] || []).include? column
attributes[:class] << 'has-editor' columns += edit_column(row, column, value, attributes, data, options)
raw_value = row[:raw_values][column] || value else
columns += content_tag(:td, value, attributes)
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
end end
columns += content_tag(:td, value, attributes)
end end
end end
pad_columns(columns, padding) pad_columns(columns, padding)
end 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 = {}) def excel_sub_tables(row, data, padding = {}, options = {})
rows = '' rows = ''
@ -1747,6 +1776,15 @@ module ApplicationHelper
rows.html_safe rows.html_safe
end 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 def registrations_table_options
{ {
id: 'search-table', id: 'search-table',

20
app/views/conferences/admin/_stats.html.haml

@ -19,7 +19,19 @@
.actions .actions
= link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :stats, :format => :xlsx), class: [:button, :download] = 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] = link_to (_'links.download.Organizations_Excel'), administration_step_path(@this_conference.slug, :organizations, :format => :xlsx), class: [:button, :download, :subdued]
%h4 Registrations %h4=_'articles.admin.stats.headings.Registrations'
= searchfield :search, nil, big: true .goes-fullscreen#registrations-table
.table-scroller .flex-column
= html_table @excel_data, registrations_table_options = 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

4
config/locales/en.yml

@ -882,7 +882,8 @@ en:
vegan: Vegans vegan: Vegans
registrations: Number of registrations registrations: Number of registrations
completed_registrations: Number of registrations completed_registrations: Number of registrations
incomplete_registrations: Incomplete registrations completed_registrations: Number of registrations
Registrations: Registrations
meals: meals:
description: On this page you can schedule the meals that you will be serving. 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. no_locations_warning: Before you can add meals, you must first add locations.
@ -1116,6 +1117,7 @@ en:
preregistered: Preregistered preregistered: Preregistered
registered: Registered registered: Registered
companion: Companion companion: Companion
companion_email: Companion Email
Preferred_Languages: Preferred Language Preferred_Languages: Preferred Language
is_attending: Attending? is_attending: Attending?
about_bikebike: about_bikebike:

Loading…
Cancel
Save