Browse Source

Schedule divisions and workshops table

development
Godwin 7 years ago
parent
commit
ad3136a5cb
  1. 1
      app/assets/images/admin/workshops.svg
  2. 218
      app/assets/javascripts/schedule.js
  3. 5
      app/assets/stylesheets/_admin.scss
  4. 134
      app/controllers/application_controller.rb
  5. 321
      app/controllers/conference_administration_controller.rb
  6. 7
      app/helpers/admin_helper.rb
  7. 6
      app/helpers/form_helper.rb
  8. 33
      app/helpers/table_helper.rb
  9. 2
      app/views/conference_administration/_events.html.haml
  10. 147
      app/views/conference_administration/_schedule.html.haml
  11. 4
      app/views/conference_administration/_select_workshop_table.html.haml
  12. 12
      app/views/conference_administration/_workshops.html.haml
  13. 4
      config/locales/en.yml

1
app/assets/images/admin/workshops.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 26.25"><path d="M0 21h24V0H0v21zM19 1h4v3h-4V1zm0 4h4v3h-4V5zm0 4h4v3h-4V9zm0 4h4v3h-4v-3zm0 4h4v3h-4v-3zM14 1h4v3h-4V1zm0 4h4v3h-4V5zm0 4h4v3h-4V9zm0 4h4v3h-4v-3zm0 4h4v3h-4v-3zM9 1h4v3H9V1zm0 4h4v3H9V5zm0 4h4v3H9V9zm0 4h4v3H9v-3zm0 4h4v3H9v-3zM1 1h7v3H1V1zm0 4h7v3H1V5zm0 4h7v3H1V9zm0 4h7v3H1v-3zm0 4h7v3H1v-3z"/></svg>

After

Width:  |  Height:  |  Size: 377 B

218
app/assets/javascripts/schedule.js

@ -1,114 +1,114 @@
(function() { (function() {
function closeWorkshopSelector() { function closeWorkshopSelector() {
document.getElementById('workshop-selector').classList.remove('open'); document.getElementById('workshop-selector').classList.remove('open');
document.body.classList.remove('modal-open'); document.body.classList.remove('modal-open');
} }
document.getElementById('workshop-selector').addEventListener('click', function(event) { document.getElementById('workshop-selector').addEventListener('click', function(event) {
if (event.target.id == 'workshop-selector') { if (event.target.id == 'workshop-selector') {
closeWorkshopSelector(); closeWorkshopSelector();
} }
}); });
function _post(form, params, f) { function _post(form, params, f) {
var request = new XMLHttpRequest(); var request = new XMLHttpRequest();
request.onreadystatechange = function() { request.onreadystatechange = function() {
if (request.readyState == 4) { if (request.readyState == 4) {
if (request.status == 200) { if (request.status == 200) {
f(request.responseText); f(request.responseText);
} }
} }
} }
request.open('POST', form.getAttribute('action'), true); request.open('POST', form.getAttribute('action'), true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
params['authenticity_token'] = form.querySelector('[name="authenticity_token"]').value; params['authenticity_token'] = form.querySelector('[name="authenticity_token"]').value;
var data = []; var data = [];
for (var key in params) { for (var key in params) {
data.push(key + '=' + params[key]); data.push(key + '=' + params[key]);
} }
request.send(data.join('&')); request.send(data.join('&'));
} }
function selectorMatches(el, selector) { function selectorMatches(el, selector) {
var p = Element.prototype; var p = Element.prototype;
var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) { var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) {
return [].indexOf.call(document.querySelectorAll(s), this) !== -1; return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
}; };
return f.call(el, selector); return f.call(el, selector);
} }
function updateSchedule(html) { function updateSchedule(html) {
var schedule = document.getElementById('schedule-preview'); var schedule = document.getElementById('schedule-preview');
var s = document.createElement('div'); var s = document.createElement('div');
s.innerHTML = html; s.innerHTML = html;
schedule.innerHTML = s.children[0].innerHTML; schedule.innerHTML = s.children[0].innerHTML;
schedule.classList.remove('requesting'); schedule.classList.remove('requesting');
} }
document.body.addEventListener('submit', function (event) { document.body.addEventListener('submit', function (event) {
if (event.target.classList.contains('deschedule-workshop')) { if (event.target.classList.contains('deschedule-workshop')) {
event.preventDefault(); event.preventDefault();
var schedule = document.getElementById('schedule-preview'); var schedule = document.getElementById('schedule-preview');
var form = event.target; var form = event.target;
schedule.classList.add('requesting'); schedule.classList.add('requesting');
_post( _post(
form, form,
{ {
id: form.querySelector('[name="id"]').value, id: form.querySelector('[name="id"]').value,
button: 'deschedule_workshop' button: 'deschedule_workshop'
}, },
updateSchedule updateSchedule
); );
} }
}); });
document.body.addEventListener('click', function (event) { document.body.addEventListener('click', function (event) {
//console.log(event.target);
if (selectorMatches(event.target, 'td.workshop.open, td.workshop.open *')) {
if (selectorMatches(event.target, 'td.workshop.open, td.workshop.open *')) { var button = event.target;
//event.stopPropagation(); while (button && button.tagName && button.tagName !== 'TD') {
var button = event.target; button = button.parentElement;
while (button && button.tagName && button.tagName !== 'TD') { }
button = button.parentElement;
}
document.getElementById('workshop-selector').classList.add('open'); document.getElementById('workshop-selector').classList.add('open');
var table = document.getElementById('table'); var table = document.getElementById('table');
table.classList.add('loading'); table.classList.add('loading');
document.body.classList.add('modal-open'); document.body.classList.add('modal-open');
var block = button.getAttribute('data-block'); var block = button.getAttribute('data-block');
var day = button.getAttribute('data-day'); var day = button.getAttribute('data-day');
var location = button.getAttribute('data-location'); var location = button.getAttribute('data-location');
var division = button.getAttribute('data-division');
_post(
document.getElementById('workshop-table-form'), _post(
{ document.getElementById('workshop-table-form'),
block: block, {
day: day, block: block,
location: location, day: day,
button: 'get-workshop-list' location: location,
}, division: division,
function (response) { button: 'get-workshop-list'
var table = document.getElementById('table'); },
table.innerHTML = response; function (response) {
table.classList.remove('loading'); var table = document.getElementById('table');
forEachElement('tr.selectable', function(row) { table.innerHTML = response;
row.addEventListener('click', function(event) { table.classList.remove('loading');
var schedule = document.getElementById('schedule-preview'); forEachElement('tr.selectable', function(row) {
schedule.classList.add('requesting'); row.addEventListener('click', function(event) {
closeWorkshopSelector(); var schedule = document.getElementById('schedule-preview');
var form = document.getElementById('workshop-table-form'); schedule.classList.add('requesting');
_post( closeWorkshopSelector();
form, var form = document.getElementById('workshop-table-form');
{ _post(
workshop: row.getAttribute('data-workshop'), form,
block: block, {
day: day, workshop: row.getAttribute('data-workshop'),
location: form.querySelector('#event_location').value, block: block,
button: 'set-workshop' day: day,
}, location: form.querySelector('#event_location').value,
updateSchedule button: 'set-workshop'
); },
}); updateSchedule
}, table); );
} });
); }, table);
} }
}, true); );
}
}, true);
})(); })();

5
app/assets/stylesheets/_admin.scss

@ -1370,7 +1370,6 @@ nav.sub-menu {
.event-detail-link { .event-detail-link {
width: auto; width: auto;
font-size: 1.25em; font-size: 1.25em;
} }
.event-detail-link, .details, .title { .event-detail-link, .details, .title {
display: inline; display: inline;
@ -1403,6 +1402,10 @@ nav.sub-menu {
width: 15em; width: 15em;
} }
li {
white-space: normal;
}
+ .event-detail-link { + .event-detail-link {
padding-right: 1em; padding-right: 1em;
} }

134
app/controllers/application_controller.rb

@ -1,5 +1,5 @@
class ApplicationController < BaseController class ApplicationController < BaseController
protect_from_forgery with: :exception, :except => [:do_confirm, :js_error, :admin_update] protect_from_forgery with: :exception, except: [:do_confirm, :js_error, :admin_update]
before_filter :application_setup before_filter :application_setup
after_filter :capture_page_info after_filter :capture_page_info
@ -392,13 +392,13 @@ class ApplicationController < BaseController
def get_scheule_data(do_analyze = true) def get_scheule_data(do_analyze = true)
conference = @this_conference || @conference conference = @this_conference || @conference
@meals = Hash[(conference.meals || {}).map{ |k, v| [k.to_i, v] }].sort.to_h @meals = Hash[(conference.meals || {}).map{ |k, v| [k.to_i, v] }].sort.to_h
@events = Event.where(:conference_id => conference.id).order(start_time: :asc) @events = Event.where(conference_id: conference.id).order(start_time: :asc)
@workshops = Workshop.where(:conference_id => conference.id).order(start_time: :asc) @workshops = Workshop.where(conference_id: conference.id).order(start_time: :asc)
@locations = {} @locations = {}
get_block_data get_block_data
@schedule = {} schedule = {}
day_1 = conference.start_date.wday day_1 = conference.start_date.wday
@workshop_blocks.each_with_index do |info, block| @workshop_blocks.each_with_index do |info, block|
@ -407,11 +407,11 @@ class ApplicationController < BaseController
day_diff += 7 if day_diff < 0 day_diff += 7 if day_diff < 0
day = (conference.start_date + day_diff.days).to_date day = (conference.start_date + day_diff.days).to_date
time = info['time'].to_f time = info['time'].to_f
@schedule[day] ||= { times: {}, locations: {} } schedule[day] ||= { times: {}, locations: {} }
@schedule[day][:times][time] ||= {} schedule[day][:times][time] ||= {}
@schedule[day][:times][time][:type] = :workshop schedule[day][:times][time][:type] = :workshop
@schedule[day][:times][time][:length] = info['length'].to_f schedule[day][:times][time][:length] = info['length'].to_f
@schedule[day][:times][time][:item] = { block: block, workshops: {} } schedule[day][:times][time][:item] = { block: block, workshops: {} }
end end
end end
@ -423,9 +423,9 @@ class ApplicationController < BaseController
day_diff += 7 if day_diff < 0 day_diff += 7 if day_diff < 0
day = (conference.start_date + day_diff.days).to_date day = (conference.start_date + day_diff.days).to_date
if block.present? && @schedule[day].present? && @schedule[day][:times].present? && @schedule[day][:times][block['time'].to_f].present? if block.present? && schedule[day].present? && schedule[day][:times].present? && schedule[day][:times][block['time'].to_f].present?
@schedule[day][:times][block['time'].to_f][:item][:workshops][workshop.event_location_id] = { workshop: workshop, status: { errors: [], warnings: [], conflict_score: nil } } schedule[day][:times][block['time'].to_f][:item][:workshops][workshop.event_location_id] = { workshop: workshop, status: { errors: [], warnings: [], conflict_score: nil } }
@schedule[day][:locations][workshop.event_location_id] ||= workshop.event_location if workshop.event_location.present? schedule[day][:locations][workshop.event_location_id] ||= workshop.event_location if workshop.event_location.present?
end end
end end
end end
@ -433,50 +433,50 @@ class ApplicationController < BaseController
@meals.each do |time, meal| @meals.each do |time, meal|
day = meal['day'].to_date day = meal['day'].to_date
time = meal['time'].to_f time = meal['time'].to_f
@schedule[day] ||= {} schedule[day] ||= {}
@schedule[day][:times] ||= {} schedule[day][:times] ||= {}
@schedule[day][:times][time] ||= {} schedule[day][:times][time] ||= {}
@schedule[day][:times][time][:type] = :meal schedule[day][:times][time][:type] = :meal
@schedule[day][:times][time][:length] = (meal['length'] || 1.0).to_f schedule[day][:times][time][:length] = (meal['length'] || 1.0).to_f
@schedule[day][:times][time][:item] = meal schedule[day][:times][time][:item] = meal
end end
@events.each do |event| @events.each do |event|
if event.present? && event.start_time.present? && event.end_time.present? if event.present? && event.start_time.present? && event.end_time.present?
day = event.start_time.midnight.to_date day = event.start_time.midnight.to_date
time = event.start_time.hour.to_f + (event.start_time.min / 60.0) time = event.start_time.hour.to_f + (event.start_time.min / 60.0)
@schedule[day] ||= {} schedule[day] ||= {}
@schedule[day][:times] ||= {} schedule[day][:times] ||= {}
@schedule[day][:times][time] ||= {} schedule[day][:times][time] ||= {}
@schedule[day][:times][time][:type] = :event schedule[day][:times][time][:type] = :event
@schedule[day][:times][time][:length] = (event.end_time - event.start_time) / 3600.0 schedule[day][:times][time][:length] = (event.end_time - event.start_time) / 3600.0
@schedule[day][:times][time][:item] = event schedule[day][:times][time][:item] = event
end end
end end
@schedule = @schedule.sort.to_h schedule = schedule.sort.to_h
@schedule.each do |day, data| schedule.each do |day, data|
@schedule[day][:times] = data[:times].sort.to_h schedule[day][:times] = data[:times].sort.to_h
end end
@schedule.each do |day, data| schedule.each do |day, data|
last_event = nil last_event = nil
data[:times].each do |time, time_data| data[:times].each do |time, time_data|
if last_event.present? if last_event.present?
@schedule[day][:times][last_event][:next_event] = time schedule[day][:times][last_event][:next_event] = time
end end
last_event = time last_event = time
end end
@schedule[day][:num_locations] = (data[:locations] || []).size schedule[day][:num_locations] = (data[:locations] || []).size
end end
@schedule.deep_dup.each do |day, data| schedule.deep_dup.each do |day, data|
data[:times].each do |time, time_data| data[:times].each do |time, time_data|
if time_data[:next_event].present? || time_data[:length] > @this_conference.schedule_interval if time_data[:next_event].present? || time_data[:length] > @this_conference.schedule_interval
span = @this_conference.schedule_interval span = @this_conference.schedule_interval
length = time_data[:next_event].present? ? time_data[:next_event] - time : time_data[:length] length = time_data[:next_event].present? ? time_data[:next_event] - time : time_data[:length]
while span < length while span < length
@schedule[day][:times][time + span] ||= { schedule[day][:times][time + span] ||= {
type: (span >= time_data[:length] ? :empty : :nil), type: (span >= time_data[:length] ? :empty : :nil),
length: @this_conference.schedule_interval length: @this_conference.schedule_interval
} }
@ -486,16 +486,20 @@ class ApplicationController < BaseController
end end
end end
@schedule = @schedule.sort.to_h # schedule = schedule.sort.to_h
@schedule.each do |day, data| schedule.each do |day, data|
@schedule[day][:times] = data[:times].sort.to_h # @schedule[day] = [{}]
@schedule[day][:locations] ||= {} # division = 0
# schedule[day][:num_locations] = schedule[day][:num_locations]
# schedule[day][:times] = data[:times].sort.to_h
schedule[day][:times] = data[:times].sort.to_h
schedule[day][:locations] ||= {}
# # sort the locations by name
schedule[day][:locations] = schedule[day][:locations].sort_by { |event_id, event| event.present? ? event.title.downcase : '' }.to_h
# sort the locations by name # # add an empty block if no workshops are scheduled on this day yet
@schedule[day][:locations] = @schedule[day][:locations].sort_by { |event_id, event| event.present? ? event.title.downcase : '' }.to_h # schedule[day][:locations][0] = :add if do_analyze || schedule[day][:locations].empty?
# add an empty block if no workshops are scheduled on this day yet
@schedule[day][:locations][0] = :add if do_analyze || @schedule[day][:locations].empty?
if do_analyze if do_analyze
data[:times].each do |time, time_data| data[:times].each do |time, time_data|
@ -512,8 +516,8 @@ class ApplicationController < BaseController
workshop_i.active_facilitators.each do |facilitator_i| workshop_i.active_facilitators.each do |facilitator_i|
workshop_j.active_facilitators.each do |facilitator_j| workshop_j.active_facilitators.each do |facilitator_j|
if facilitator_i.id == facilitator_j.id if facilitator_i.id == facilitator_j.id
@schedule[day][:times][time][:status] ||= {} schedule[day][:times][time][:status] ||= {}
@schedule[day][:times][time][:item][:workshops][ids[j]][:status][:errors] << { schedule[day][:times][time][:item][:workshops][ids[j]][:status][:errors] << {
name: :common_facilitator, name: :common_facilitator,
facilitator: facilitator_i, facilitator: facilitator_i,
workshop: workshop_i, workshop: workshop_i,
@ -533,7 +537,7 @@ class ApplicationController < BaseController
amenities = JSON.parse(location.amenities || '[]').map &:to_sym amenities = JSON.parse(location.amenities || '[]').map &:to_sym
needs.each do |need| needs.each do |need|
@schedule[day][:times][time][:item][:workshops][ids[i]][:status][:errors] << { schedule[day][:times][time][:item][:workshops][ids[i]][:status][:errors] << {
name: :need_not_available, name: :need_not_available,
need: need, need: need,
location: location, location: location,
@ -555,13 +559,55 @@ class ApplicationController < BaseController
end end
end end
@schedule[day][:times][time][:item][:workshops][ids[i]][:status][:conflict_score] = (interests & (workshop_i.interested.map { | u | u.user_id })).length schedule[day][:times][time][:item][:workshops][ids[i]][:status][:conflict_score] = (interests & (workshop_i.interested.map { | u | u.user_id })).length
end end
end end
end end
end end
end end
end end
@schedule = {}
schedule.sort.to_h.each do |day, data|
@schedule[day] = []
division = 0
# @schedule[day][division] = data
locations = nil
@schedule[day][division] = {}
@schedule[day][division][:times] = {}
# @schedule[day][division][:times] = data[:times]
# # sort the locations by name
# @schedule[day][:locations] = schedule[day][:locations].sort_by { |event_id, event| event.present? ? event.title.downcase : '' }.to_h
# # add an empty block if no workshops are scheduled on this day yet
# schedule[day][:locations][0] = :add if do_analyze || schedule[day][:locations].empty?
# last_time_data = nil
data[:times].each do |time, time_data|
if time_data[:type] == :workshop && time_data[:item].present? && time_data[:item][:workshops].present?
if !locations.nil? && ((locations.keys - time_data[:item][:workshops].keys) | (time_data[:item][:workshops].keys - locations.keys)).length > 0
# data[:locations]
# xxx
@schedule[day][division][:locations] = locations.deep_dup
@schedule[day][division][:locations][0] = :add if do_analyze || locations.empty?
locations = data[:locations].select { |id, l| time_data[:item][:workshops][id].present? }
division += 1
@schedule[day][division] = {}
@schedule[day][division][:times] = {}
else
locations = data[:locations].select { |id, l| time_data[:item][:workshops][id].present? }
end
end
# last_time_data = time_data
@schedule[day][division][:times][time] = time_data
end
locations ||= data[:locations]
@schedule[day][division][:locations] = locations
@schedule[day][division][:locations][0] = :add if do_analyze || locations.empty?
@schedule[day][division][:num_locations] = locations.length
end
end end
protected protected

321
app/controllers/conference_administration_controller.rb

@ -253,46 +253,7 @@ class ConferenceAdministrationController < ApplicationController
format.xlsx { render xlsx: '../conferences/stats', filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" } format.xlsx { render xlsx: '../conferences/stats', filename: "stats-#{DateTime.now.strftime('%Y-%m-%d')}" }
end end
else else
if params[:sort_column] sort_data(params[:sort_column], params[:sort_dir], :name)
col = params[:sort_column].to_sym
@excel_data[:data].sort_by! do |row|
value = row[col]
if row[:raw_values].key?(col)
value = if row[:raw_values][col].is_a?(TrueClass)
't'
elsif row[:raw_values][col].is_a?(FalseClass)
''
else
row[:raw_values][col]
end
elsif value.is_a?(City)
value = value.sortable_string
end
if value.nil?
case @excel_data[:column_types][col]
when :datetime, [:date, :day]
value = Date.new
when :money
value = 0
else
value = ''
end
end
value
end
if params[:sort_dir] == 'up'
@sort_dir = :up
@excel_data[:data].reverse!
end
@sort_column = col
else
@sort_column = :name
end
end end
@registration_count = @registrations.size @registration_count = @registrations.size
@ -324,6 +285,22 @@ class ConferenceAdministrationController < ApplicationController
end end
end end
def administrate_workshops
get_workshops(true)
if request.format.xlsx?
logger.info "Generating stats.xls"
return respond_to do |format|
format.xlsx { render xlsx: '../conferences/stats', filename: "workshops-#{DateTime.now.strftime('%Y-%m-%d')}" }
end
else
sort_data(params[:sort_column], params[:sort_dir], :name)
end
if request.xhr?
render html: view_context.html_table(@excel_data, view_context.registrations_table_options)
end
end
def administrate_check_in def administrate_check_in
sort_weight = { sort_weight = {
checked_in: 5, checked_in: 5,
@ -769,6 +746,179 @@ class ConferenceAdministrationController < ApplicationController
end end
end end
def get_workshops(html_format = false, id = nil, conference = @this_conference)
@workshops = conference.workshops.sort_by { |w| w.title.downcase }
@excel_data = {
columns: [
:title,
:owner,
:locale,
:date,
:info,
:notes,
:facilitators
] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } +
Workshop.all_needs.map { |n| "need_#{n}".to_sym } + [
:theme,
:space
] + (User.AVAILABLE_LANGUAGES - [I18n.locale]).map { |l| "title_#{l}".to_sym } +
(User.AVAILABLE_LANGUAGES - [I18n.locale]).map { |l| "info_#{l}".to_sym },
column_types: {
title: :bold,
date: :datetime,
info: :text,
notes: :text,
owner: :email
},
keys: {
title: 'forms.labels.generic.title',
owner: 'roles.workshops.facilitator.creator',
locale: 'articles.conference_registration.terms.Preferred_Languages',
info: 'forms.labels.generic.info',
date: 'workshop.created_at',
notes: 'forms.labels.generic.notes',
facilitators: 'roles.workshops.facilitator.facilitator',
theme: 'articles.workshops.headings.theme',
space: 'articles.workshops.headings.space'
},
data: []
}
@excel_data[:key_vars] = {}
User.AVAILABLE_LANGUAGES.each do |l|
@excel_data[:keys]["language_#{l}".to_sym] = "languages.#{l}"
if l != I18n.locale
@excel_data[:keys]["title_#{l}".to_sym] = 'translate.content.item_translation'
@excel_data[:key_vars]["title_#{l}".to_sym] = { language: view_context.language_name(l), item: I18n.t('forms.labels.generic.title') }
@excel_data[:keys]["info_#{l}".to_sym] = 'translate.content.item_translation'
@excel_data[:key_vars]["info_#{l}".to_sym] = { language: view_context.language_name(l), item: I18n.t('forms.labels.generic.info') }
@excel_data[:column_types]["info_#{l}".to_sym] = :text
end
end
Workshop.all_needs.each do |n|
@excel_data[:keys]["need_#{n}".to_sym] = "workshop.options.needs.#{n}"
end
@workshops.each do |w|
if w.present?
if id.nil? || id == w.id
owner = User.find(w.creator)
facilitators = w.collaborators.map { |f| User.find(f) }
data = {
id: w.id,
title: w.title,
info: view_context.strip_tags(w.info),
notes: view_context.strip_tags(w.notes),
owner: owner.name,
locale: w.locale.present? ? (view_context.language_name w.locale) : '',
date: w.created_at ? w.created_at.strftime("%F %T") : '',
facilitators: facilitators.map { |f| f.name }.join(', '),
theme: w.theme && Workshop.all_themes.include?(w.theme.to_sym) ? I18n.t("workshop.options.theme.#{w.theme}") : w.theme,
space: w.space && Workshop.all_spaces.include?(w.space.to_sym) ? I18n.t("workshop.options.space.#{w.space}") : '',
raw_values: {
info: w.info,
owner: owner.email,
notes: w.notes,
locale: w.locale,
facilitators: facilitators.map { |f| f.email }.join(', '),
theme: w.theme,
space: w.space
},
html_values: {
}
}
languages = JSON.parse(w.languages || '[]').map &:to_sym
User.AVAILABLE_LANGUAGES.each do |l|
in_language = ((languages || []).include? l.to_sym)
data["language_#{l}".to_sym] = (in_language ? I18n.t('articles.conference_registration.questions.bike.yes') : '')
data[:raw_values]["language_#{l}".to_sym] = in_language
if l != I18n.locale
data["title_#{l}".to_sym] = w.get_column_for_locale!(:title, l, false)
data["info_#{l}".to_sym] = view_context.strip_tags(w.get_column_for_locale!(:info, l, false))
data[:raw_values]["info_#{l}".to_sym] = w.get_column_for_locale!(:info, l, false)
end
end
needs = JSON.parse(w.needs || '[]').map &:to_sym
Workshop.all_needs.each do |n|
in_need = ((needs || []).include? n.to_sym)
data["need_#{n}".to_sym] = (in_need ? I18n.t('articles.conference_registration.questions.bike.yes') : '')
data[:raw_values]["need_#{n}".to_sym] = in_need
end
@excel_data[:data] << data
end
end
end
if html_format
@column_options = {
locale: I18n.backend.enabled_locales.map { |l| [(view_context.language_name l), l] },
theme: Workshop.all_themes.map { |t| [I18n.t("workshop.options.theme.#{t}"), t] },
space: Workshop.all_spaces.map { |s| [I18n.t("workshop.options.space.#{s}"), s] }
}
@column_options[:theme] += ((conference.workshops.map { |w| w.theme }) - Workshop.all_themes.map(&:to_s)).uniq.map { |t| [t, t] }
User.AVAILABLE_LANGUAGES.each do |l|
@column_options["language_#{l}".to_sym] = [
[I18n.t("articles.conference_registration.questions.bike.yes"), true]
]
end
Workshop.all_needs.each do |n|
@column_options["need_#{n}".to_sym] = [
[I18n.t("articles.conference_registration.questions.bike.yes"), true]
]
end
end
end
def sort_data(col, sort_dir, default_col)
if col
col = col.to_sym
@excel_data[:data].sort_by! do |row|
value = row[col]
if row[:raw_values].key?(col)
value = if row[:raw_values][col].is_a?(TrueClass)
't'
elsif row[:raw_values][col].is_a?(FalseClass)
''
elsif @excel_data[:column_types][col] == :text
view_context.strip_tags(row[:raw_values][col] || '').downcase
else
row[:raw_values][col]
end
elsif value.is_a?(City)
value = value.sortable_string
end
if value.nil?
case @excel_data[:column_types][col]
when :datetime, [:date, :day]
value = Date.new
when :money
value = 0
else
value = ''
end
end
value
end
if sort_dir == 'up'
@sort_dir = :up
@excel_data[:data].reverse!
end
@sort_column = col
else
@sort_column = default_col
end
end
def get_housing_data def get_housing_data
@hosts = {} @hosts = {}
@guests = {} @guests = {}
@ -1255,6 +1405,85 @@ class ConferenceAdministrationController < ApplicationController
return nil return nil
end end
def admin_update_workshops
if params[:button] == 'update'
workshop = Workshop.where(
id: params[:key].to_i,
conference_id: @this_conference.id
).limit(1).first
params.each do |key, value|
case key.to_sym
when :owner
user = User.get(value.strip)
user_role = WorkshopFacilitator.where(user_id: user.id, workshop_id: workshop.id).first || WorkshopFacilitator.new(user_id: user.id, workshop_id: workshop.id)
owner_role = WorkshopFacilitator.where(role: :creator, workshop_id: workshop.id).first
if !owner_role || owner_role.user_id != user.id
owner_role.role = :collaborator
user_role.role = :creator
owner_role.save!
user_role.save!
end
when :facilitators
ids = []
value.split(/[\s,;]+/).each do |email|
user = User.get(email)
ids << user.id
user_role = WorkshopFacilitator.where(user_id: user.id, workshop_id: workshop.id).first || WorkshopFacilitator.new(user_id: user.id, workshop_id: workshop.id)
unless user_role.role == 'creator' || user_role.role == 'collaborator'
user_role.role = 'collaborator'
user_role.save
end
end
WorkshopFacilitator.where("workshop_id = ? AND role = ? AND user_id NOT IN (?)", workshop.id, 'collaborator', ids).destroy_all
when :title, :locale, :date, :info, :notes, :theme, :space
workshop.send("#{key}=", value.present? ? value : nil)
else
if key.start_with?('language_')
l = key.split('_').last.to_sym
languages = JSON.parse(workshop.languages || '[]').map &:to_sym
if User.AVAILABLE_LANGUAGES.include? l
if value.present?
languages |= [l]
else
languages -= [l]
end
workshop.languages = languages.to_json
end
elsif key.start_with?('need_')
n = key.split('_').last.to_sym
needs = JSON.parse(workshop.needs || '[]').map &:to_sym
if Workshop.all_needs.include? n
if value.present?
needs |= [n]
else
needs -= [n]
end
workshop.needs = needs.to_json
end
elsif key.start_with?('title_')
l = key.split('_').last.to_sym
workshop.set_column_for_locale(:title, l, value)
elsif key.start_with?('info_')
l = key.split('_').last.to_sym
workshop.set_column_for_locale(:info, l, value)
end
end
end
workshop.save!
get_workshops(true, params[:key].to_i)
options = view_context.workshops_table_options
options[:html] = true
render html: view_context.excel_rows(@excel_data, {}, options)
else
do_404
end
return nil
end
def admin_update_check_in def admin_update_check_in
unless params[:button] == 'cancel' unless params[:button] == 'cancel'
user_id = params[:user_id] user_id = params[:user_id]
@ -1478,7 +1707,7 @@ class ConferenceAdministrationController < ApplicationController
end end
(params[:title] || {}).each do |locale, value| (params[:title] || {}).each do |locale, value|
event.set_column_for_locale(:title, locale, html_value(value), current_user.id) if value != event._title(locale) && view_context.strip_tags(value).strip.present? event.set_column_for_locale(:title, locale, html_value(value), current_user.id) if value != event._title(locale) && view_context.strip.present?
end end
event.save event.save
@ -1555,8 +1784,18 @@ class ConferenceAdministrationController < ApplicationController
@time = @workshop_blocks[@block]['time'].to_f @time = @workshop_blocks[@block]['time'].to_f
@day = (Date.parse params[:day]) @day = (Date.parse params[:day])
@location = params[:location] @location = params[:location]
@division = params[:division].to_i
@event_location = @location.present? && @location.to_i > 0 ? EventLocation.find(@location.to_i) : nil @event_location = @location.present? && @location.to_i > 0 ? EventLocation.find(@location.to_i) : nil
@schedule ||= {}
@schedule[@day] ||= {}
@schedule[@day][@division] ||= []
@schedule[@day][@division][:times] ||= {}
@schedule[@day][@division][:times][@time] ||= {}
@schedule[@day][@division][:times][@time][:item] ||= {}
@schedule[@day][@division][:times][@time][:item][:workshops] || {}
@invalid_locations = @schedule[@day][@division.to_i][:times][@time][:item][:workshops].keys
@workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }.each do |workshop| @workshops.sort { |a, b| a.title.downcase <=> b.title.downcase }.each do |workshop|
@ordered_workshops[workshop.id] = workshop @ordered_workshops[workshop.id] = workshop
end end

7
app/helpers/admin_helper.rb

@ -38,7 +38,8 @@ module AdminHelper
events: [ events: [
:locations, :locations,
:meals, :meals,
:events :events,
:workshops
], ],
schedule: [ schedule: [
:workshop_times, :workshop_times,
@ -177,8 +178,8 @@ module AdminHelper
def available_dates_match?(host, guest) def available_dates_match?(host, guest)
return false unless host.housing_data['availability'].present? && host.housing_data['availability'][1].present? return false unless host.housing_data['availability'].present? && host.housing_data['availability'][1].present?
return false unless guest.arrival.present? && guest.departure.present? return false unless guest.arrival.present? && guest.departure.present?
if host.housing_data['availability'][0] <= guest.arrival && if host.housing_data['availability'][0].to_date <= guest.arrival.to_date &&
host.housing_data['availability'][1] >= guest.departure host.housing_data['availability'][1].to_date >= guest.departure.to_date
return true return true
end end

6
app/helpers/form_helper.rb

@ -11,13 +11,15 @@ module FormHelper
# set the selected locale # set the selected locale
selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym selected_locale = (options[:locale] || object.locale || I18n.locale).to_sym
I18n.backend.enabled_locales.each do |locale| locales = I18n.backend.enabled_locales.map { |l| [l, _("languages.#{l}")] }.sort_by { |l| l.last.downcase }.to_h
locales.each do |locale, locale_name|
# ses if this should b the selected field # ses if this should b the selected field
class_name = selected_locale == locale.to_sym ? 'selected' : nil class_name = selected_locale == locale.to_sym ? 'selected' : nil
# add the locale to the nav # add the locale to the nav
nav += content_tag(:li, nav += content_tag(:li,
content_tag(:a, _("languages.#{locale}"), href: 'javascript:void(0)'), content_tag(:a, locale_name, href: 'javascript:void(0)'),
class: class_name, data: { locale: locale }).html_safe class: class_name, data: { locale: locale }).html_safe
fields = '' fields = ''

33
app/helpers/table_helper.rb

@ -16,7 +16,7 @@ module TableHelper
headers = '' headers = ''
options[:column_names].each do |header_name, columns| options[:column_names].each do |header_name, columns|
column_names[header_name] ||= [] column_names[header_name] ||= []
headers += content_tag(:th, excel_data[:keys][header_name].present? ? _(excel_data[:keys][header_name]) : '', colspan: 2) headers += content_tag(:th, excel_data[:keys][header_name].present? ? I18n.t(excel_data[:keys][header_name], (excel_data[:key_vars] || {})[header_name]) : '', colspan: 2)
row_count = columns.size row_count = columns.size
columns.each do |column| columns.each do |column|
column_names[header_name] << column column_names[header_name] << column
@ -43,7 +43,7 @@ module TableHelper
attributes[:rowspan] = options[:row_spans][column] attributes[:rowspan] = options[:row_spans][column]
end end
column_text = excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '' column_text = excel_data[:keys][column].present? ? I18n.t(excel_data[:keys][column], (excel_data[:key_vars] || {})[header_name]) : ''
columns_html += content_tag(:th, column_text.html_safe, rowspan: attributes[:rowspan]) + columns_html += content_tag(:th, column_text.html_safe, rowspan: attributes[:rowspan]) +
edit_column(nil, column, nil, attributes, excel_data, options) edit_column(nil, column, nil, attributes, excel_data, options)
@ -64,7 +64,7 @@ module TableHelper
if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column) if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column)
rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do
attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } }
column_text = excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '' column_text = excel_data[:keys][column].present? ? I18n.t(excel_data[:keys][column], (excel_data[:key_vars] || {})[header_name]) : ''
columns = content_tag(:th, column_text.html_safe) + edit_column(nil, column, nil, attributes, excel_data, options) columns = content_tag(:th, column_text.html_safe) + edit_column(nil, column, nil, attributes, excel_data, options)
end end
@ -124,7 +124,7 @@ module TableHelper
data[:columns].each do |column| data[:columns].each do |column|
unless data[:column_types].present? && data[:column_types][column] == :table unless data[:column_types].present? && data[:column_types][column] == :table
column_text = data[:keys][column].present? ? _(data[:keys][column]) : '' column_text = data[:keys][column].present? ? I18n.t(data[:keys][column], (data[:key_vars] || {})[column]) : ''
attrs = { class: class_name } attrs = { class: class_name }
unless @sort_column.nil? unless @sort_column.nil?
@ -400,4 +400,29 @@ module TableHelper
column_options: @column_options column_options: @column_options
} }
end end
def workshops_table_options
{
id: 'search-table',
class: ['registrations', 'admin-edit'],
primary_key: :id,
column_names: [
:title,
:owner,
:info,
:notes,
:locale,
:facilitators
] +
User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } +
(User.AVAILABLE_LANGUAGES - [I18n.locale]).map { |l| "title_#{l}".to_sym } +
Workshop.all_needs.map { |n| "need_#{n}".to_sym } + [
:theme,
:space
],
editable: administration_update_path(@this_conference.slug, @admin_step),
sortable: administration_step_path(@this_conference.slug, @admin_step),
column_options: @column_options
}
end
end end

2
app/views/conference_administration/_events.html.haml

@ -35,7 +35,7 @@
= day_select @day, small: true, format: :weekday = day_select @day, small: true, format: :weekday
= hour_select @time, small: true = hour_select @time, small: true
= length_select @length, small: true = length_select @length, small: true
= translate_fields @event, { title: { type: :textfield, big: true, label: 'forms.labels.generic.title' }, info: { type: :textarea, label: 'forms.labels.generic.info', edit_on: :focus } } = translate_fields @event, { title: { type: :textfield, big: true }, info: { type: :textarea, label: 'forms.labels.generic.info', edit_on: :focus } }
.actions.next-prev .actions.next-prev
= button :save, value: :save = button :save, value: :save
= button :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present? = button :cancel, value: :cancel, class: :subdued, formnovalidate: true if @event.id.present?

147
app/views/conference_administration/_schedule.html.haml

@ -6,81 +6,82 @@
- else - else
- add_inline_script :schedule if @entire_page - add_inline_script :schedule if @entire_page
#schedule-preview #schedule-preview
- @schedule.each do |day, data| - @schedule.each do |day, data_array|
%h4=date(day, :weekday).html_safe %h4=date(day, :weekday).html_safe
%table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]} - data_array.each_with_index do |data, division|
- if data[:locations].present? && data[:locations].values.first != :add %table.schedule{class: [data[:locations].present? ? 'has-locations' : 'no-locations', "locations-#{data[:num_locations]}"]}
%thead - if data[:locations].present? && data[:locations].values.first != :add
%tr %thead
%th.corner %tr
- data[:locations].each do |id, location| %th.corner
%th=location.is_a?(Symbol) ? '' : _!(location.title)
%tbody
- data[:times].each do |time, time_data|
%tr{class: "row-type-#{time_data[:type] || 'nil'}"}
- rowspan = (time_data[:length] * (1 / @this_conference.schedule_interval)).to_i
%th=time(time).html_safe
- if time_data[:type] == :workshop
- data[:locations].each do |id, location| - data[:locations].each do |id, location|
- if time_data[:item][:workshops][id].present? %th=location.is_a?(Symbol) ? '' : _!(location.title)
- workshop = time_data[:item][:workshops][id][:workshop] %tbody
- status = time_data[:item][:workshops][id][:status] - data[:times].each do |time, time_data|
- else %tr{class: "row-type-#{time_data[:type] || 'nil'}"}
- workshop = status = nil - rowspan = (time_data[:length] * (1 / @this_conference.schedule_interval)).to_i
%td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id }} %th=time(time).html_safe
- if workshop.present? && workshop.event_location.present? - if time_data[:type] == :workshop
.workshop-container - data[:locations].each do |id, location|
- if @can_edit - if time_data[:item][:workshops][id].present?
-if strip_tags(workshop.notes).strip.present? - workshop = time_data[:item][:workshops][id][:workshop]
= admin_notes(workshop.notes) - status = time_data[:item][:workshops][id][:status]
- if status[:errors].present? - else
= admin_status content_tag(:ul, (status[:errors].collect { |error| (_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars])}.join).html_safe).html_safe - workshop = status = nil
= link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do %td{class: [time_data[:type], workshop.present? ? :filled : :open], rowspan: rowspan, data: workshop.present? ? nil : { block: time_data[:item][:block], day: day, location: id, division: division }}
.details - if workshop.present? && workshop.event_location.present?
.title=_!workshop.title .workshop-container
%template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }} - if @can_edit
%h1.title=_!workshop.title -if strip_tags(workshop.notes).strip.present?
%p.address = admin_notes(workshop.notes)
= _!("#{workshop.event_location.title}:") - if status[:errors].present?
= location_link workshop.event_location = admin_status content_tag(:ul, (status[:errors].collect { |error| "<li>#{(_"errors.messages.schedule.#{error[:name].to_s}", vars: error[:i18nVars])}</li>"}.join).html_safe).html_safe
.workshop-description= richtext workshop.info, 1 = link_to view_workshop_path(@conference.slug, workshop.id), class: 'event-detail-link' do
- if @can_edit .details
= form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do .title=_!workshop.title
.status %template.event-details{data: { href: view_workshop_path(@conference.slug, workshop.id) }}
.conflict-score %h1.title=_!workshop.title
%span.title Conflicts: %p.address
%span.value="#{status[:conflict_score]} / #{workshop.interested.size}" = _!("#{workshop.event_location.title}:")
= hidden_field_tag :id, workshop.id = location_link workshop.event_location
= button :deschedule, value: :deschedule_workshop, class: [:delete, :small] .workshop-description= richtext workshop.info, 1
- elsif @can_edit - if @can_edit
.title="Block #{time_data[:item][:block] + 1}" = form_tag administration_update_path(conference.slug, @admin_step), class: 'deschedule-workshop' do
- elsif time_data[:type] != :nil .status
%td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1} .conflict-score
- case time_data[:type] %span.title Conflicts:
- when :meal %span.value="#{status[:conflict_score]} / #{workshop.interested.size}"
- location = EventLocation.where(id: time_data[:item]['location'].to_i).first = hidden_field_tag :id, workshop.id
- if location.present? = button :deschedule, value: :deschedule_workshop, class: [:delete, :small]
%a.event-detail-link - elsif @can_edit
.details .title="Block #{time_data[:item][:block] + 1}"
.title=_!(time_data[:item]['title']) - elsif time_data[:type] != :nil
.location=_!location.title %td{class: time_data[:type], rowspan: rowspan, colspan: data[:locations].present? ? data[:locations].size : 1}
%template.event-details - case time_data[:type]
%h1.title=_!(time_data[:item]['title']) - when :meal
%p.address - location = EventLocation.where(id: time_data[:item]['location'].to_i).first
= _!("#{location.title}:") - if location.present?
= location_link location %a.event-detail-link
- when :event .details
- if time_data[:item].event_location.present? .title=_!(time_data[:item]['title'])
%a.event-detail-link .location=_!location.title
.details %template.event-details
.title=_!(time_data[:item][:title]) if time_data[:item][:title] %h1.title=_!(time_data[:item]['title'])
.location=_!(time_data[:item].event_location.title) %p.address
%template.event-details = _!("#{location.title}:")
%h1.title=_!(time_data[:item][:title]) if time_data[:item][:title] = location_link location
%p.address - when :event
= _!("#{time_data[:item].event_location.title}:") - if time_data[:item].event_location.present?
= location_link time_data[:item].event_location %a.event-detail-link
= richtext time_data[:item][:info], 1 .details
.title=_!(time_data[:item][:title]) if time_data[:item][:title]
.location=_!(time_data[:item].event_location.title)
%template.event-details
%h1.title=_!(time_data[:item][:title]) if time_data[:item][:title]
%p.address
= _!("#{time_data[:item].event_location.title}:")
= location_link time_data[:item].event_location
= richtext time_data[:item][:info], 1
- if @entire_page - if @entire_page
#workshop-selector #workshop-selector
= form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do = form_tag administration_update_path(@this_conference.slug, @admin_step), class: 'workshop-dlg', id: 'workshop-table-form' do

4
app/views/conference_administration/_select_workshop_table.html.haml

@ -5,7 +5,7 @@
= @event_location.title = @event_location.title
= hidden_field_tag :event_location, @location = hidden_field_tag :event_location, @location
- else - else
= location_select(nil, inline_label: true, small: true, invalid_locations: (((((@schedule[@day] || {})[:times] || {})[@time] || {})[:item] || {})[:workshops] || {}).keys, label: false) = location_select(nil, inline_label: true, small: true, invalid_locations: @invalid_locations, label: false)
- if @event_location.present? - if @event_location.present?
.host-field .host-field
%h4.inline=_'articles.admin.locations.headings.amenities' %h4.inline=_'articles.admin.locations.headings.amenities'
@ -38,7 +38,7 @@
%td=(workshop.active_facilitators.map { |x| x.named_email }).join(', ') %td=(workshop.active_facilitators.map { |x| x.named_email }).join(', ')
%td=workshop.interested_count %td=workshop.interested_count
%td %td
.text=workshop.notes .text=strip_tags(workshop.notes)
.legend .legend
%h4 Legend %h4 Legend

12
app/views/conference_administration/_workshops.html.haml

@ -0,0 +1,12 @@
- add_inline_script :registrations
= columns(medium: 12) do
.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'
.table-scroller#registrations
= html_table(@excel_data, workshops_table_options)
= columns(medium: 12) do
.actions.center
= link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :workshops, format: :xlsx), class: [:button, :download]

4
config/locales/en.yml

@ -1427,6 +1427,7 @@ en:
locations: Locations locations: Locations
meals: Meals meals: Meals
events: Events events: Events
workshops: Workshops
descriptions: descriptions:
locations: Create the list of locations that you will be using for events, locations: Create the list of locations that you will be using for events,
meals, and workshops. meals, and workshops.
@ -1435,6 +1436,7 @@ en:
events: Create event details. These events should be any type of event other events: Create event details. These events should be any type of event other
than meals and workshops such as meeting, rides, and parties. The events than meals and workshops such as meeting, rides, and parties. The events
will be added to your schedule. will be added to your schedule.
workshops: View and modify all workshops created by registered users.
schedule: schedule:
description: On this page you can schedule workshops and publish your schedule description: On this page you can schedule workshops and publish your schedule
to the front page. to the front page.
@ -2625,6 +2627,7 @@ en:
unregistered: Unregistered unregistered: Unregistered
facilitator: Facilitator facilitator: Facilitator
workshop: workshop:
created_at: Creation Date
options: options:
needs: needs:
projector: Projector projector: Projector
@ -2718,6 +2721,7 @@ en:
content: content:
change_locale: Read in %{language} change_locale: Read in %{language}
Translation_of: Translation of Translation_of: Translation of
item_translation: "%{item} in %{language}"
number: number:
currency: currency:
format: format:

Loading…
Cancel
Save