Browse Source

Admin improvements

development
Godwin 8 years ago
parent
commit
9e55d27d33
  1. 12
      app/assets/javascripts/main.js
  2. 348
      app/assets/javascripts/registrations.js
  3. 285
      app/assets/stylesheets/_admin.scss
  4. 111
      app/assets/stylesheets/_application.scss
  5. 2
      app/assets/stylesheets/bumbleberry-settings.json
  6. 154
      app/controllers/conference_administration_controller.rb
  7. 22
      app/helpers/admin_helper.rb
  8. 2
      app/helpers/geocoder_helper.rb
  9. 3
      app/helpers/registration_helper.rb
  10. 67
      app/helpers/table_helper.rb
  11. 155
      app/helpers/widgets_helper.rb
  12. 4
      app/views/application/user_settings.html.haml
  13. 13
      app/views/conference_administration/_hosts_table.html.haml
  14. 2
      app/views/conference_administration/_housing.html.haml
  15. 8
      app/views/conference_administration/_registrations.html.haml
  16. 102
      app/views/conference_administration/_select_guest_table.html.haml
  17. 6
      app/views/conference_administration/administration.html.haml
  18. 4
      app/views/conference_administration/administration_step.html.haml
  19. 14
      app/views/conferences/index.html.haml
  20. 31
      app/views/help/_admin_housing.html.haml
  21. 7
      app/views/layouts/application.html.haml
  22. 15
      config/assets_cdn.yml
  23. 2
      config/environments/development.rb
  24. 34
      config/locales/en.yml

12
app/assets/javascripts/main.js

@ -40,7 +40,9 @@
dlg.removeAttribute('aria-hidden'); dlg.removeAttribute('aria-hidden');
dlg.setAttribute('role', 'alertdialog'); dlg.setAttribute('role', 'alertdialog');
dlg.setAttribute('tabindex', '0'); dlg.setAttribute('tabindex', '0');
dlg.focus(); if (!dlg.getAttribute('data-nofocus')) {
dlg.focus();
}
setTimeout(function() { dlg.classList.add('open'); }, 100); setTimeout(function() { dlg.classList.add('open'); }, 100);
} }
window.closeOverlay = function(dlg, primaryContent, body) { window.closeOverlay = function(dlg, primaryContent, body) {
@ -114,6 +116,14 @@
return false; return false;
}); });
}); });
var helpDlg = document.getElementById('help-dlg');
forEachElement('[data-help-text]', function(link) {
link.addEventListener('click', function(event) {
event.preventDefault();
openDlg(helpDlg, link);
return false;
});
});
var loginDlg = document.getElementById('login-dlg'); var loginDlg = document.getElementById('login-dlg');
forEachElement('[data-sign-in]', function(link) { forEachElement('[data-sign-in]', function(link) {
link.addEventListener('click', function(event) { link.addEventListener('click', function(event) {

348
app/assets/javascripts/registrations.js

@ -1,168 +1,200 @@
(function() { (function() {
var searchControl = document.getElementById('search'); var searchControl = document.getElementById('search');
function filterTable() { function filterTable() {
forEach(document.getElementById('search-table').getElementsByTagName('TBODY')[0].getElementsByTagName('TR'), function(tr) { forEach(document.getElementById('search-table').getElementsByTagName('TBODY')[0].getElementsByTagName('TR'), function(tr) {
if (tr.classList.contains('editable')) { if (tr.classList.contains('editable')) {
tr.classList.remove('hidden'); tr.classList.remove('hidden');
var value = searchControl.value; var value = searchControl.value;
if (value) { if (value) {
var words = value.split(/\s+/); var words = value.split(/\s+/);
for (var i = 0; i < words.length; i++) { for (var i = 0; i < words.length; i++) {
var word = new RegExp(words[i].replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), "i"); var word = new RegExp(words[i].replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), "i");
if (tr.innerHTML.search(word) == -1) { if (tr.innerHTML.search(word) == -1) {
tr.classList.add('hidden'); tr.classList.add('hidden');
} }
} }
} }
} }
}); });
} }
// ref = https://davidwalsh.name/element-matches-selector // ref = https://davidwalsh.name/element-matches-selector
function selectorMatches(el, selector) { function selectorMatches(el, selector) {
var p = Element.prototype; if (el instanceof Element) {
var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) { var p = Element.prototype;
return [].indexOf.call(document.querySelectorAll(s), this) !== -1; var f = p.matches || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || function(s) {
}; return [].indexOf.call(document.querySelectorAll(s), this) !== -1;
return f.call(el, selector); };
} return f.call(el, selector);
}
return false;
}
function saveRow(row) { function saveRow(row) {
if (row) { if (row) {
row.classList.remove('editing'); row.classList.remove('editing');
var table = row.parentElement.parentElement; var table = row.parentElement.parentElement;
var editRow = row.nextSibling; var editRow = row.nextSibling;
var url = table.getAttribute('data-update-url'); var url = table.getAttribute('data-update-url');
var data = new FormData(); var data = new FormData();
var request = new XMLHttpRequest(); var request = new XMLHttpRequest();
request.onreadystatechange = function() { request.onreadystatechange = function() {
if (request.readyState == 4) { if (request.readyState == 4) {
row.classList.remove('requesting'); row.classList.remove('requesting');
if (request.status == 200 && request.responseText) { if (request.status == 200 && request.responseText) {
var tempTable = document.createElement('table'); var tempTable = document.createElement('table');
tempTable.innerHTML = request.responseText; tempTable.innerHTML = request.responseText;
var rows = tempTable.getElementsByTagName('tr'); var rows = tempTable.getElementsByTagName('tr');
row.innerHTML = rows[0].innerHTML; row.innerHTML = rows[0].innerHTML;
editRow.innerHTML = rows[1].innerHTML; editRow.innerHTML = rows[1].innerHTML;
} }
} }
} }
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'); 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')) {
data.append(cells[i].getAttribute('name'), cells[i].value); data.append(cells[i].getAttribute('name'), cells[i].value);
changed = true; changed = true;
} }
} }
if (changed) { if (changed) {
row.classList.add('requesting'); row.classList.add('requesting');
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send(data); request.send(data);
} }
} }
} }
function editTableCell(cell) { function sortBy(table, column, dir) {
if (cell && selectorMatches(cell, 'tr[data-key].editable td')) { if (!dir) {
editTableRow(cell.parentElement, cell); dir = 'down';
} else if (!cell || !selectorMatches(cell, 'tr[data-key].editable + tr, tr[data-key].editable + tr *')) { } else if (dir == 'down') {
var currentRow = document.querySelector('tr[data-key].editable.editing'); dir = 'up';
if (currentRow) { } else {
saveRow(currentRow); dir = 'down';
} }
}
}
function editTableRow(row, cell) {
if (selectorMatches(row, 'tr[data-key].editable')) {
var key = row.getAttribute('data-key');
var currentRow = document.querySelector('tr[data-key].editable.editing');
if (currentRow && currentRow.getAttribute('data-key') !== key) {
saveRow(currentRow);
}
var editor = row.nextSibling;
if (!row.classList.contains('editing')) {
row.classList.add('editing');
var focusElement = null;
if (cell) {
focusElement = editor.querySelector('td[data-column-id="' + cell.getAttribute('data-column-id') + '"] .cell-editor');
}
focusElement = focusElement || editor.getElementsByClassName('cell-editor')[0];
focusElement.focus();
if (focusElement.tagName === 'TEXTAREA' || (focusElement.tagName === 'INPUT' && focusElement.type != 'number' && focusElement.type != 'email')) {
focusElement.setSelectionRange(0, focusElement.value.length);
}
}
}
}
document.addEventListener('click', function (event) { editTableCell(event.target); });
document.addEventListener('keyup', function (event) {
if (event.code === "Enter") {
var currentRow = document.querySelector('tr[data-key].editable.editing');
if (currentRow) {
event.stopPropagation();
event.preventDefault();
var next = event.shiftKey ? 'previousSibling' : 'nextSibling';
var cell = document.activeElement.parentElement.getAttribute('data-column-id');
var row = currentRow[next] ? currentRow[next][next] : null;
if (!row) {
rows = currentRow.parentElement.children;
row = event.shiftKey ? rows[rows.length - 2] : rows[0];
}
editTableRow(row, row.querySelector('[data-column-id="' + cell + '"]'));
}
} else if (event.code === "Escape") {
var currentRow = document.querySelector('tr[data-key].editable.editing');
if (currentRow) {
event.stopPropagation();
event.preventDefault();
saveRow(currentRow);
}
}
});
if (document.observe) {
document.observe("focusin", function (event) { editTableCell(event.target); });
} else {
document.addEventListener("focus", function (event) { editTableCell(event.target); }, true);
}
window.onbeforeunload = function() { var url = table.getAttribute('data-sort-url') + '?sort_column=' + column + '&sort_dir=' + dir;
editTableCell(); var tableContainer = table.parentElement;
};
searchControl.addEventListener('keyup', filterTable); tableContainer.classList.add('requesting');
searchControl.addEventListener('search', filterTable); var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState == 4) {
tableContainer.classList.remove('requesting');
if (request.status == 200 && request.responseText) {
tableContainer.innerHTML = request.responseText;
}
}
}
request.open('GET', url, true);
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.send();
}
forEachElement('[data-expands]', function(button) { function editTableCell(cell) {
button.addEventListener('click', function(event) { if (cell && selectorMatches(cell, 'tr[data-key].editable td')) {
var element = document.getElementById(event.target.getAttribute('data-expands')); editTableRow(cell.parentElement, cell);
document.body.classList.add('expanded-element'); } else if (cell && selectorMatches(cell, 'th[data-colname]')) {
element.classList.add('expanded'); sortBy(cell.parentElement.parentElement.parentElement, cell.getAttribute('data-colname'), cell.getAttribute('data-dir'));
}); } else if (!cell || !selectorMatches(cell, 'tr[data-key].editable + tr, tr[data-key].editable + tr *')) {
}); var currentRow = document.querySelector('tr[data-key].editable.editing');
forEachElement('[data-contracts]', function(button) { if (currentRow) {
button.addEventListener('click', function(event) { saveRow(currentRow);
var element = document.getElementById(event.target.getAttribute('data-contracts')); }
document.body.classList.remove('expanded-element'); }
element.classList.remove('expanded'); }
}); function editTableRow(row, cell) {
}); if (selectorMatches(row, 'tr[data-key].editable')) {
forEachElement('[data-opens-modal]', function(button) { var key = row.getAttribute('data-key');
button.addEventListener('click', function(event) { var currentRow = document.querySelector('tr[data-key].editable.editing');
var element = document.getElementById(event.target.getAttribute('data-opens-modal')); if (currentRow && currentRow.getAttribute('data-key') !== key) {
document.body.classList.add('modal-open'); saveRow(currentRow);
element.classList.add('open'); }
}); var editor = row.nextSibling;
}); if (!row.classList.contains('editing')) {
forEachElement('[data-closes-modal]', function(element) { row.classList.add('editing');
element.addEventListener('click', function(event) { var focusElement = null;
document.getElementById(event.target.getAttribute('data-closes-modal')).classList.remove('open'); if (cell) {
document.body.classList.remove('modal-open'); focusElement = editor.querySelector('td[data-column-id="' + cell.getAttribute('data-column-id') + '"] .cell-editor');
}); }
}); focusElement = focusElement || editor.getElementsByClassName('cell-editor')[0];
focusElement.focus();
if (focusElement.tagName === 'TEXTAREA' || (focusElement.tagName === 'INPUT' && focusElement.type != 'number' && focusElement.type != 'email')) {
focusElement.setSelectionRange(0, focusElement.value.length);
}
}
}
}
document.addEventListener('click', function (event) { editTableCell(event.target); });
document.addEventListener('keyup', function (event) {
if (event.code === "Enter") {
var currentRow = document.querySelector('tr[data-key].editable.editing');
if (currentRow) {
event.stopPropagation();
event.preventDefault();
var next = event.shiftKey ? 'previousSibling' : 'nextSibling';
var cell = document.activeElement.parentElement.getAttribute('data-column-id');
var row = currentRow[next] ? currentRow[next][next] : null;
if (!row) {
rows = currentRow.parentElement.children;
row = event.shiftKey ? rows[rows.length - 2] : rows[0];
}
editTableRow(row, row.querySelector('[data-column-id="' + cell + '"]'));
}
} else if (event.code === "Escape") {
var currentRow = document.querySelector('tr[data-key].editable.editing');
if (currentRow) {
event.stopPropagation();
event.preventDefault();
saveRow(currentRow);
}
}
});
if (document.observe) {
document.observe("focusin", function (event) { editTableCell(event.target); });
} else {
document.addEventListener("focus", function (event) { editTableCell(event.target); }, true);
}
window.onbeforeunload = function() {
editTableCell();
};
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');
});
});
})(); })();

285
app/assets/stylesheets/_admin.scss

@ -29,6 +29,61 @@ nav.sub-nav {
} }
table, .table { table, .table {
tr.spacer td {
border: 0;
height: 0.5em;
}
&[data-sort-url] {
[data-colname] {
position: relative;
cursor: pointer;
white-space: nowrap;
@include after {
content: '';
position: absolute;
bottom: -1em;
left: 50%;
font-size: 1.5em;
opacity: 0;
z-index: 2;
margin-left: -0.25em;
pointer-events: none;
@include _(transition, opacity 150ms ease-in-out);
}
&:hover {
@include after {
opacity: 1;
}
}
}
[data-dir] {
@include after {
opacity: 0.5;
}
&:hover {
@include after {
content: '';
}
}
}
[data-dir="up"] {
@include after {
content: '';
}
&:hover {
@include after {
content: '';
}
}
}
}
th, td, .table-th, .table-td { th, td, .table-th, .table-td {
&.center { &.center {
text-align: center; text-align: center;
@ -51,45 +106,50 @@ table, .table {
width: 1.75em; width: 1.75em;
&.happy { &.happy {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 87.8 73.2'><polygon fill='#{$colour-5}' points='34.3 73.2 0 32.6 18.7 16.8 35.4 36.5 70.1 0 87.8 16.8 '/></svg>"); @include after {
content: '\1F601';
opacity: 0.5;
}
} }
&.unhappy { &.unhappy {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path xmlns="http://www.w3.org/2000/svg" d="M50 5C25.1 5 5 25.1 5 50c0 24.9 20.1 45 45 45s45-20.1 45-45C95 25.1 74.9 5 50 5zM72.5 64.3l-8.2 8.2L50 58.2 35.7 72.5l-8.2-8.2L41.8 50 27.5 35.7l8.2-8.2L50 41.8l14.3-14.3 8.2 8.2L58.2 50 72.5 64.3z" fill="#{$colour-4}"/></svg>'); @include after {
content: '\1F621';
}
} }
} }
} }
td, .table-td { td, .table-td {
&.inner-table { &.inner-table {
padding: 0; padding: 0.5em;
table { table {
margin: 0; margin: 0;
width: 100%; width: 100%;
} }
tr:first-child { // tr:first-child {
td, th { // td, th {
border-top: 0; // border-top: 0;
} // }
} // }
tr:last-child { // tr:last-child {
td, th { // td, th {
border-bottom: 0; // border-bottom: 0;
} // }
} // }
td, th { // td, th {
&:first-child { // &:first-child {
border-left: 0 // border-left: 0
} // }
&:last-child { // &:last-child {
border-right: 0 // border-right: 0
} // }
} // }
} }
&.bold { &.bold {
@ -359,6 +419,7 @@ body.expanded-element {
z-index: 1002; z-index: 1002;
background-color: #F8F8F8; background-color: #F8F8F8;
flex: 1; flex: 1;
padding-bottom: 1em;
} }
.actions { .actions {
@ -636,6 +697,12 @@ nav.sub-menu {
} }
} }
#registrations-table {
.button {
margin-top: 0;
}
}
#main article #registration-admin-menu { #main article #registration-admin-menu {
margin: 1em 0 0; margin: 1em 0 0;
padding: 0; padding: 0;
@ -745,6 +812,15 @@ nav.sub-menu {
} }
} }
@include keyframes(unhappy) {
from {
@include _(transform, rotate(15deg));
}
to {
@include _(transform, rotate(-15deg));
}
}
#admin-housing, #admin-schedule { #admin-housing, #admin-schedule {
.guests-housed { .guests-housed {
margin-bottom: 1em; margin-bottom: 1em;
@ -759,12 +835,33 @@ nav.sub-menu {
.data { .data {
display: inline-block; display: inline-block;
font-size: 1.125em; font-size: 1.125em;
@include after {
margin-left: 0.5em;
font-size: 1.5em;
}
&.happy {
@include after {
content: '\1F60D';
}
}
&.unhappy {
@include after {
content: '\1F61E';
}
}
} }
} }
#housing-table { #housing-table {
@include _(transition, opacity 1s ease-in-out); @include _(transition, opacity 1s ease-in-out);
table {
margin-left: 0;
}
&.loading { &.loading {
@include _(opacity, 0.5); @include _(opacity, 0.5);
pointer-events: none; pointer-events: none;
@ -780,6 +877,10 @@ nav.sub-menu {
text-align: right; text-align: right;
@include font-family(primary); @include font-family(primary);
} }
.name {
min-width: 10em;
}
} }
} }
@ -788,28 +889,17 @@ nav.sub-menu {
td { td {
background-color: lighten($colour-1, 40%); background-color: lighten($colour-1, 40%);
&:hover {
background-color: $colour-1;
}
&.full { &.full {
background-color: $gray; background-color: $gray;
&:hover { .button {
background-color: #CCC; background-color: #888;
} }
} }
} }
a { .button {
display: block; display: inline-block;
color: $white;
text-align: center;
@include font-family(secondary);
@include after {
display: none;
}
} }
} }
@ -819,15 +909,31 @@ nav.sub-menu {
} }
td { td {
vertical-align: top; vertical-align: middle;
} }
.state { .state {
position: relative; position: relative;
font-family: inherit; font-family: inherit;
padding: 0;
position: relative;
width: 2em;
height: 2em;
@include after {
position: absolute;
bottom: 0;
left: 0;
font-size: 1.5em;
}
&.unhappy { &.unhappy {
cursor: pointer; cursor: pointer;
@include after {
@include _(transform-origin, bottom);
@include _(animation, unhappy ease-in-out 1s infinite alternate both);
}
} }
ul { ul {
@ -837,7 +943,7 @@ nav.sub-menu {
top: 0; top: 0;
background-color: $white; background-color: $white;
border: 0.1em solid #CCC; border: 0.1em solid #CCC;
padding: 0.25em 0.75em 0.25em 1.5em; padding: 0.25em 0.75em;
margin: 0; margin: 0;
list-style-type: square; list-style-type: square;
@include default-box-shadow(top, 2); @include default-box-shadow(top, 2);
@ -846,7 +952,12 @@ nav.sub-menu {
li { li {
white-space: nowrap; white-space: nowrap;
margin: 0; margin: 0 0 0 1em;
&:first-child:last-child {
list-style: none;
margin: 0;
}
} }
&:hover { &:hover {
@ -893,25 +1004,71 @@ nav.sub-menu {
background-color: $white; background-color: $white;
width: 80%; width: 80%;
margin: auto; margin: auto;
padding: 1em;
height: 80%; height: 80%;
cursor: default; cursor: default;
@include default-box-shadow(top, 2); @include default-box-shadow(top, 2);
h3 { h3 {
text-align: center;
margin: 0 0 1em; margin: 0 0 1em;
padding: 0.5em 0.6667em;
color: $white;
background-color: $green;
@include _(text-stroke, 1px rgba(0, 0, 0, 0.25));
} }
} }
} }
} }
.table-scroller.no-edit {
box-shadow: none;
background-color: transparent;
table {
margin-bottom: 0;
}
}
#table, #help-dlg {
.legend ul {
@include _-(display, flex);
list-style: none;
padding: 0;
li {
@include _(flex, 1);
text-align: center;
margin-bottom: 0.5em;
padding: 0.125em 0.5em;
margin: 0.1em;
border: 0.1em solid $light-gray;
background-color: #F8F8F8;
@include font-family(secondary);
&.other-host, &.other-space, &.bad-match {
opacity: 0.5;
}
&.selected-space, &.other-space {
background-color: $colour-5;
}
&.other-host {
background-color: $colour-1;
}
}
}
}
#table { #table {
display: flex;
flex-direction: column;
position: relative; position: relative;
overflow: auto; overflow: auto;
height: 80%; height: 80%;
height: calc(100% - 4em); height: calc(100% - 6.5em);
background-color: $white; background-color: $white;
margin: 1em;
@include _(transition, background-color 250ms ease-in-out); @include _(transition, background-color 250ms ease-in-out);
&.loading { &.loading {
@ -924,7 +1081,7 @@ nav.sub-menu {
} }
table { table {
margin: 0 0 2em; margin: 0 0 1em;
} }
h4 { h4 {
@ -992,45 +1149,27 @@ nav.sub-menu {
} }
} }
.legend ul { .p {
@include _-(display, flex); max-height: 4em;
list-style: none; overflow: auto;
padding: 0; }
li {
@include _(flex, 1);
text-align: center;
margin-bottom: 0.5em;
padding: 0.125em 0.5em;
margin: 0.1em;
border: 0.1em solid $light-gray;
background-color: #F8F8F8;
@include font-family(secondary);
&.other-host, &.other-space, &.bad-match { .guest-table {
opacity: 0.5; flex: 1;
} display: flex;
}
&.selected-space, &.other-space { td, th {
background-color: $colour-5; white-space: nowrap;
}
&.other-host { &.break-ok {
background-color: $colour-1; white-space: normal;
} min-width: 10em;
} }
} }
.p {
max-height: 4em;
overflow: auto;
}
} }
#admin-housing { #admin-housing {
#table table {
min-width: 100em;
}
#hosts { #hosts {
background-color: $white; background-color: $white;

111
app/assets/stylesheets/_application.scss

@ -141,21 +141,6 @@ table, .table {
background-color: transparent; background-color: transparent;
border: 0; border: 0;
} }
&.state {
background-size: 1.333em;
background-repeat: no-repeat;
background-position: center;
width: 1.75em;
&.happy {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 87.8 73.2'><polygon fill='#{$colour-5}' points='34.3 73.2 0 32.6 18.7 16.8 35.4 36.5 70.1 0 87.8 16.8 '/></svg>");
}
&.unhappy {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path xmlns="http://www.w3.org/2000/svg" d="M50 5C25.1 5 5 25.1 5 50c0 24.9 20.1 45 45 45s45-20.1 45-45C95 25.1 74.9 5 50 5zM72.5 64.3l-8.2 8.2L50 58.2 35.7 72.5l-8.2-8.2L41.8 50 27.5 35.7l8.2-8.2L50 41.8l14.3-14.3 8.2 8.2L58.2 50 72.5 64.3z" fill="#{$colour-4}"/></svg>');
}
}
} }
th, .table-th { th, .table-th {
@ -167,42 +152,42 @@ table, .table {
} }
} }
td, .table-td { // td, .table-td {
&.inner-table { // &.inner-table {
padding: 0; // padding: 0;
table { // table {
margin: 0; // margin: 0;
width: 100%; // width: 100%;
} // }
tr:first-child { // tr:first-child {
td, th { // td, th {
border-top: 0; // border-top: 0;
} // }
} // }
tr:last-child { // tr:last-child {
td, th { // td, th {
border-bottom: 0; // border-bottom: 0;
} // }
} // }
td, th { // td, th {
&:first-child { // &:first-child {
border-left: 0 // border-left: 0
} // }
&:last-child { // &:last-child {
border-right: 0 // border-right: 0
} // }
} // }
} // }
&.bold { // &.bold {
@include font-family(secondary); // @include font-family(secondary);
} // }
} // }
tbody th { tbody th {
width: 0.1rem; width: 0.1rem;
@ -2449,13 +2434,14 @@ a.logo {
.register-link { .register-link {
font-size: 1.25em; font-size: 1.25em;
margin: 0.5em; margin: 0.5em;
.button {
@include _(animation, radiate 2s linear infinite alternate);
}
} }
} }
.help-link {
float: right;
background-color: $red;
}
.conference-details { .conference-details {
.links { .links {
text-align: center; text-align: center;
@ -2906,6 +2892,29 @@ body {
text-align: left; text-align: left;
} }
#help-dlg {
.dlg-content {
@include _-(display, flex);
@include _(flex-direction, column);
max-width: 60rem;
}
.dlg-inner {
overflow: auto;
}
.title {
background-color: $red;
font-size: 2em;
text-align: left;
}
.message {
text-align: left;
font-size: 1.125em;
}
}
#info-dlg .message { #info-dlg .message {
text-align: left; text-align: left;
@ -2956,10 +2965,6 @@ body {
to { background-position: 60px 30px; } to { background-position: 60px 30px; }
} }
@include keyframes(radiate) {
to { background-color: $green; }
}
html :focus { html :focus {
outline: 0; outline: 0;
} }

2
app/assets/stylesheets/bumbleberry-settings.json

@ -10,7 +10,7 @@
"and_chr": ["59"], "and_chr": ["59"],
"chrome": ["59"], "chrome": ["59"],
"edge": ["13"], "edge": ["13"],
"firefox": ["50"], "firefox": ["52"],
"ie": ["11"], "ie": ["11"],
"ios_saf": ["8", "9"] "ios_saf": ["8", "9"]
} }

154
app/controllers/conference_administration_controller.rb

@ -1,7 +1,10 @@
require 'geocoder/calculations' require 'geocoder/calculations'
require 'rest_client' require 'rest_client'
require 'registration_controller_helper'
class ConferenceAdministrationController < ApplicationController class ConferenceAdministrationController < ApplicationController
include RegistrationControllerHelper
def administration def administration
set_conference set_conference
return do_403 unless @this_conference.host? current_user return do_403 unless @this_conference.host? current_user
@ -224,6 +227,47 @@ class ConferenceAdministrationController < ApplicationController
return respond_to do |format| return respond_to do |format|
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
if params[:sort_column]
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
@ -249,6 +293,10 @@ class ConferenceAdministrationController < ApplicationController
end end
end end
end end
if request.xhr?
render html: view_context.html_table(@excel_data, view_context.registrations_table_options)
end
end end
def administrate_stats def administrate_stats
@ -372,6 +420,7 @@ class ConferenceAdministrationController < ApplicationController
return respond_to do |format| return respond_to do |format|
format.xlsx { render xlsx: '../conferences/stats', filename: "housing" } format.xlsx { render xlsx: '../conferences/stats', filename: "housing" }
end end
else
end end
end end
@ -428,16 +477,21 @@ class ConferenceAdministrationController < ApplicationController
columns: [ columns: [
:name, :name,
:email, :email,
:date,
:status, :status,
:is_attending, :is_attending,
:is_subscribed, :is_subscribed,
:registration_fees_paid, :registration_fees_paid,
:date, :payment_currency,
:payment_method,
:city, :city,
:preferred_language :preferred_language
] + ] +
User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym } +
[ [
:group_ride,
:organization,
:org_non_member_interest,
:arrival, :arrival,
:departure, :departure,
:housing, :housing,
@ -451,8 +505,7 @@ class ConferenceAdministrationController < ApplicationController
:last_day, :last_day,
:address, :address,
:phone :phone
] + ConferenceRegistration.all_spaces + ] + ConferenceRegistration.all_spaces + [
ConferenceRegistration.all_considerations + [
:notes :notes
], ],
column_types: { column_types: {
@ -460,6 +513,7 @@ class ConferenceAdministrationController < ApplicationController
date: :datetime, date: :datetime,
email: :email, email: :email,
companion_email: :email, companion_email: :email,
org_non_member_interest: :text,
arrival: [:date, :day], arrival: [:date, :day],
departure: [:date, :day], departure: [:date, :day],
registration_fees_paid: :money, registration_fees_paid: :money,
@ -476,6 +530,9 @@ class ConferenceAdministrationController < ApplicationController
is_subscribed: 'articles.user_settings.headings.email_subscribe', is_subscribed: 'articles.user_settings.headings.email_subscribe',
city: 'forms.labels.generic.event_location', city: 'forms.labels.generic.event_location',
date: 'articles.conference_registration.terms.Date', date: 'articles.conference_registration.terms.Date',
group_ride: 'articles.conference_registration.step_names.group_ride',
organization: 'articles.conference_registration.step_names.org_select',
org_non_member_interest: 'articles.conference_registration.step_names.org_non_member_interest',
preferred_language: 'articles.conference_registration.terms.Preferred_Languages', preferred_language: 'articles.conference_registration.terms.Preferred_Languages',
arrival: 'forms.labels.generic.arrival', arrival: 'forms.labels.generic.arrival',
departure: 'forms.labels.generic.departure', departure: 'forms.labels.generic.departure',
@ -485,13 +542,15 @@ class ConferenceAdministrationController < ApplicationController
companion: 'articles.conference_registration.terms.companion', companion: 'articles.conference_registration.terms.companion',
companion_email: 'articles.conference_registration.terms.companion_email', companion_email: 'articles.conference_registration.terms.companion_email',
registration_fees_paid: 'articles.conference_registration.headings.fees_paid', registration_fees_paid: 'articles.conference_registration.headings.fees_paid',
payment_currency: 'forms.labels.generic.payment_currency',
payment_method: 'forms.labels.generic.payment_method',
other: 'forms.labels.generic.other_notes', other: 'forms.labels.generic.other_notes',
can_provide_housing: 'articles.conference_registration.can_provide_housing', can_provide_housing: 'articles.conference_registration.housing_provider',
first_day: 'forms.labels.generic.first_day', first_day: 'forms.labels.generic.first_day',
last_day: 'forms.labels.generic.last_day', last_day: 'forms.labels.generic.last_day',
notes: 'forms.labels.generic.notes', notes: 'forms.labels.generic.notes',
phone: 'forms.labels.generic.phone', phone: 'forms.labels.generic.phone',
address: 'forms.labels.generic.address', address: 'forms.labels.generic.address_short',
contact_info: 'articles.conference_registration.headings.contact_info', contact_info: 'articles.conference_registration.headings.contact_info',
questions: 'articles.conference_registration.headings.questions', questions: 'articles.conference_registration.headings.questions',
hosting: 'articles.conference_registration.headings.hosting' hosting: 'articles.conference_registration.headings.hosting'
@ -517,41 +576,51 @@ class ConferenceAdministrationController < ApplicationController
user = r.user_id ? User.where(id: r.user_id).first : nil user = r.user_id ? User.where(id: r.user_id).first : nil
if user.present? if user.present?
companion = view_context.companion(r) companion = view_context.companion(r)
companion = companion.is_a?(User) ? companion.name : (view_context._"articles.conference_registration.terms.registration_status.#{companion}") if companion.present? companion = companion.is_a?(User) ? companion.name : I18n.t("articles.conference_registration.terms.registration_status.#{companion}") if companion.present?
steps = r.steps_completed || [] steps = r.steps_completed || []
if id.nil? || id == r.id if id.nil? || id == r.id
registration_data = r.data || {}
housing_data = r.housing_data || {} housing_data = r.housing_data || {}
availability = housing_data['availability'] || [] availability = housing_data['availability'] || []
availability[0] = Date.parse(availability[0]) if availability[0].present? availability[0] = Date.parse(availability[0]) if availability[0].present?
availability[1] = Date.parse(availability[1]) if availability[1].present? availability[1] = Date.parse(availability[1]) if availability[1].present?
org = r.user.organizations.first
data = { data = {
id: r.id, id: r.id,
name: user.firstname || '', name: user.firstname || '',
email: user.email || '', email: user.email || '',
status: (view_context._"articles.conference_registration.terms.registration_status.#{view_context.registration_status(r)}"), status: I18n.t("articles.conference_registration.terms.registration_status.#{view_context.registration_status(r)}"),
is_attending: (view_context._"articles.conference_registration.questions.bike.#{r.is_attending == 'n' ? 'no' : 'yes'}"), is_attending: I18n.t("articles.conference_registration.questions.bike.#{r.is_attending == 'n' ? 'no' : 'yes'}"),
is_subscribed: user.is_subscribed == false ? (view_context._'articles.conference_registration.questions.bike.no') : '', is_subscribed: user.is_subscribed == false ? I18n.t('articles.conference_registration.questions.bike.no') : '',
date: r.created_at ? r.created_at.strftime("%F %T") : '', date: r.created_at ? r.created_at.strftime("%F %T") : '',
city: r.city || '', city: r.city || '',
preferred_language: user.locale.present? ? (view_context.language_name user.locale) : '', preferred_language: user.locale.present? ? (view_context.language_name user.locale) : '',
arrival: r.arrival ? r.arrival.strftime("%F %T") : '', arrival: r.arrival ? r.arrival.strftime("%F %T") : '',
departure: r.departure ? r.departure.strftime("%F %T") : '', departure: r.departure ? r.departure.strftime("%F %T") : '',
housing: r.housing.present? ? (view_context._"articles.conference_registration.questions.housing.#{r.housing}") : '', group_ride: registration_data['group_ride'].present? ? I18n.t("forms.actions.generic.#{registration_data['group_ride']}") : '',
bike: r.bike.present? ? (view_context._"articles.conference_registration.questions.bike.#{r.bike}") : '', organization: org.present? ? org.name : '',
food: r.food.present? ? (view_context._"articles.conference_registration.questions.food.#{r.food}") : '', org_non_member_interest: registration_data['non_member_interest'],
housing: r.housing.present? ? I18n.t("articles.conference_registration.questions.housing_short.#{r.housing}") : '',
bike: r.bike.present? ? I18n.t("articles.conference_registration.questions.bike.#{r.bike}") : '',
food: r.food.present? ? I18n.t("articles.conference_registration.questions.food.#{r.food}") : '',
companion: companion, companion: companion,
companion_email: (housing_data['companion'] || { 'email' => ''})['email'], companion_email: (housing_data['companion'] || { 'email' => ''})['email'],
registration_fees_paid: r.registration_fees_paid, registration_fees_paid: registration_data['payment_amount'],
other: r.allergies.present? ? "#{r.allergies}\n\n#{r.other}" : r.other, payment_currency: registration_data['payment_currency'],
can_provide_housing: r.can_provide_housing ? (view_context._'articles.conference_registration.questions.bike.yes') : '', payment_method: registration_data['payment_method'].present? ? I18n.t("forms.labels.generic.payment_type.#{registration_data['payment_method']}") : '',
other: [r.allergies, r.other, housing_data['other']].compact.join("\n\n"),
can_provide_housing: r.can_provide_housing.nil? ? '' : I18n.t("articles.conference_registration.questions.bike.#{r.can_provide_housing ? 'yes' : 'no'}"),
first_day: availability[0].present? ? availability[0].strftime("%F %T") : '', first_day: availability[0].present? ? availability[0].strftime("%F %T") : '',
last_day: availability[1].present? ? availability[1].strftime("%F %T") : '', last_day: availability[1].present? ? availability[1].strftime("%F %T") : '',
notes: housing_data['notes'], notes: housing_data['notes'],
address: housing_data['address'], address: housing_data['address'],
phone: housing_data['phone'], phone: housing_data['phone'],
raw_values: { raw_values: {
group_ride: registration_data['group_ride'],
registration_fees_paid: registration_data['payment_amount'].to_f,
payment_method: registration_data['payment_method'],
housing: r.housing, housing: r.housing,
bike: r.bike, bike: r.bike,
food: r.food, food: r.food,
@ -560,12 +629,13 @@ class ConferenceAdministrationController < ApplicationController
preferred_language: user.locale, preferred_language: user.locale,
is_attending: r.is_attending != 'n', is_attending: r.is_attending != 'n',
is_subscribed: user.is_subscribed, is_subscribed: user.is_subscribed,
can_provide_housing: r.can_provide_housing, can_provide_housing: r.can_provide_housing.to_s,
first_day: availability[0].present? ? availability[0].to_date : nil, first_day: availability[0].present? ? availability[0].to_date : nil,
last_day: availability[1].present? ? availability[1].to_date : nil last_day: availability[1].present? ? availability[1].to_date : nil
}, },
html_values: { html_values: {
date: r.created_at.present? ? r.created_at.strftime("%F %T") : '', date: r.created_at.present? ? r.created_at.strftime("%F %T") : '',
registration_fees_paid: registration_data['payment_amount'].present? ? view_context.number_to_currency(registration_data['payment_amount'].to_f, unit: '$') : '',
arrival: r.arrival.present? ? view_context.date(r.arrival.to_date, :span_same_year_date_1) : '', arrival: r.arrival.present? ? view_context.date(r.arrival.to_date, :span_same_year_date_1) : '',
departure: r.departure.present? ? view_context.date(r.departure.to_date, :span_same_year_date_1) : '', departure: r.departure.present? ? view_context.date(r.departure.to_date, :span_same_year_date_1) : '',
first_day: availability[0].present? ? view_context.date(availability[0].to_date, :span_same_year_date_1) : '', first_day: availability[0].present? ? view_context.date(availability[0].to_date, :span_same_year_date_1) : '',
@ -574,17 +644,13 @@ class ConferenceAdministrationController < ApplicationController
} }
User.AVAILABLE_LANGUAGES.each do |l| User.AVAILABLE_LANGUAGES.each do |l|
can_speak = ((user.languages || []).include? l.to_s) can_speak = ((user.languages || []).include? l.to_s)
data["language_#{l}".to_sym] = (can_speak ? (view_context._'articles.conference_registration.questions.bike.yes') : '') data["language_#{l}".to_sym] = (can_speak ? I18n.t('articles.conference_registration.questions.bike.yes') : '')
data[:raw_values]["language_#{l}".to_sym] = can_speak data[:raw_values]["language_#{l}".to_sym] = can_speak
end end
ConferenceRegistration.all_spaces.each do |s| ConferenceRegistration.all_spaces.each do |s|
space = (housing_data['space'] || {})[s.to_s] space = (housing_data['space'] || {})[s.to_s]
data[s] = space.present? ? space.to_i : nil data[s] = space.present? ? space.to_i : nil
end data[:raw_values][s] = space.present? ? space.to_i : 0
ConferenceRegistration.all_considerations.each do |c|
consideration = (housing_data['considerations'] || []).include?(c.to_s)
data[c] = (consideration ? (view_context._'articles.conference_registration.questions.bike.yes') : '')
data[:raw_values][c] = consideration
end end
@excel_data[:data] << data @excel_data[:data] << data
end end
@ -593,18 +659,18 @@ class ConferenceAdministrationController < ApplicationController
if html_format if html_format
yes_no = [ yes_no = [
[(view_context._"articles.conference_registration.questions.bike.yes"), true], [I18n.t('forms.actions.generic.yes'), true],
[(view_context._"articles.conference_registration.questions.bike.no"), false] [I18n.t('forms.actions.generic.no'), false]
] ]
@column_options = { @column_options = {
housing: ConferenceRegistration.all_housing_options.map { |h| [ housing: ConferenceRegistration.all_housing_options.map { |h| [
(view_context._"articles.conference_registration.questions.housing.#{h}"), I18n.t("articles.conference_registration.questions.housing_short.#{h}"),
h] }, h] },
bike: ConferenceRegistration.all_bike_options.map { |b| [ bike: ConferenceRegistration.all_bike_options.map { |b| [
(view_context._"articles.conference_registration.questions.bike.#{b}"), I18n.t("articles.conference_registration.questions.bike.#{b}"),
b] }, b] },
food: ConferenceRegistration.all_food_options.map { |f| [ food: ConferenceRegistration.all_food_options.map { |f| [
(view_context._"articles.conference_registration.questions.food.#{f}"), I18n.t("articles.conference_registration.questions.food.#{f}"),
f] }, f] },
arrival: view_context.conference_days_options_list(:before_plus_one), arrival: view_context.conference_days_options_list(:before_plus_one),
departure: view_context.conference_days_options_list(:after_minus_one), departure: view_context.conference_days_options_list(:after_minus_one),
@ -615,16 +681,19 @@ class ConferenceAdministrationController < ApplicationController
is_subscribed: [yes_no.last], is_subscribed: [yes_no.last],
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),
group_ride: [:yes, :no, :maybe].map { |o| [I18n.t("forms.actions.generic.#{o}"), o] },
payment_currency: Conference.default_currencies.map { |c| [c, c] },
payment_method: ConferenceRegistration.all_payment_methods.map { |c| [I18n.t("forms.labels.generic.payment_type.#{c}"), c] }
} }
User.AVAILABLE_LANGUAGES.each do |l| User.AVAILABLE_LANGUAGES.each do |l|
@column_options["language_#{l}".to_sym] = [ @column_options["language_#{l}".to_sym] = [
[(view_context._"articles.conference_registration.questions.bike.yes"), true] [I18n.t("articles.conference_registration.questions.bike.yes"), true]
] ]
end end
ConferenceRegistration.all_considerations.each do |c| ConferenceRegistration.all_considerations.each do |c|
@column_options[c.to_sym] = [ @column_options[c.to_sym] = [
[(view_context._"articles.conference_registration.questions.bike.yes"), true] [I18n.t("articles.conference_registration.questions.bike.yes"), true]
] ]
end end
end end
@ -633,7 +702,7 @@ class ConferenceAdministrationController < ApplicationController
def get_housing_data def get_housing_data
@hosts = {} @hosts = {}
@guests = {} @guests = {}
ConferenceRegistration.where(:conference_id => @this_conference.id).each do |registration| ConferenceRegistration.where(conference_id: @this_conference.id).each do |registration|
if registration.can_provide_housing if registration.can_provide_housing
@hosts[registration.id] = registration @hosts[registration.id] = registration
elsif registration.housing.present? && registration.housing != 'none' elsif registration.housing.present? && registration.housing != 'none'
@ -657,6 +726,7 @@ class ConferenceAdministrationController < ApplicationController
@housing_data[id][:space][s.to_sym] = size @housing_data[id][:space][s.to_sym] = size
end end
end end
@unhappy_people = Set.new
@guests_housed = 0 @guests_housed = 0
@ -691,7 +761,7 @@ class ConferenceAdministrationController < ApplicationController
if (guest.housing == 'house' && space == :tent) || if (guest.housing == 'house' && space == :tent) ||
(guest.housing == 'tent' && (space == :bed_space || space == :floor_space)) (guest.housing == 'tent' && (space == :bed_space || space == :floor_space))
@housing_data[host_id][:guest_data][guest_id][:warnings][:space] = { actual: (view_context._"forms.labels.generic.#{space.to_s}"), expected: (view_context._"articles.conference_registration.questions.housing.#{guest.housing}")} @housing_data[host_id][:guest_data][guest_id][:warnings][:space] = { actual: (view_context._"forms.labels.generic.#{space.to_s}"), expected: (view_context._"articles.conference_registration.questions.housing_short.#{guest.housing}")}
end end
if data['companion'].present? if data['companion'].present?
@ -717,6 +787,7 @@ class ConferenceAdministrationController < ApplicationController
end end
end end
end end
@unhappy_people << guest_id if @housing_data[host_id][:guest_data][guest_id][:errors].present? || @housing_data[host_id][:guest_data][guest_id][:warnings].present?
else else
# make sure the housing data is empty if the host wasn't found, just in case something happened to the host # make sure the housing data is empty if the host wasn't found, just in case something happened to the host
@guests[guest_id].housing_data ||= {} @guests[guest_id].housing_data ||= {}
@ -739,6 +810,7 @@ class ConferenceAdministrationController < ApplicationController
if @housing_data[id][:guests][space].size > space_available if @housing_data[id][:guests][space].size > space_available
@housing_data[id][:warnings][:space][space] << :overbooked @housing_data[id][:warnings][:space][space] << :overbooked
@unhappy_people << id
end end
end end
end end
@ -1021,12 +1093,22 @@ class ConferenceAdministrationController < ApplicationController
registration.city_id = city.id registration.city_id = city.id
end end
end end
when :housing, :bike, :food, :allergies, :other when :housing, :bike, :food
registration.send("#{key.to_s}=", value) registration.send("#{key}=", value)
when :other
registration.housing_data ||= {}
registration.housing_data[key] = value
# delete deprecated values
registration.allergies = nil
registration.other = nil
when :registration_fees_paid when :registration_fees_paid
registration.registration_fees_paid = value.to_i registration.data ||= {}
registration.data['payment_amount'] = value.to_f
when :group_ride, :payment_currency, :payment_method
registration.data ||= {}
registration.data[key.to_s] = value.present? ? value.to_sym : nil
when :can_provide_housing when :can_provide_housing
registration.send("#{key.to_s}=", value.present?) registration.send("#{key.to_s}=", value == 'true' ? true : (value == 'false' ? false : nil))
when :arrival, :departure when :arrival, :departure
registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil) registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil)
when :companion_email when :companion_email

22
app/helpers/admin_helper.rb

@ -160,21 +160,6 @@ module AdminHelper
end end
end end
# housing_data = guest.housing_data || []
# if housing_data['host'].present?
# if housing_data['host'] == host.id
# return space == housing_data['space'] ? :selected_space : :other_space
# end
# return :other_host
# end
# if space_matches?(space, guest.housing) && available_dates_match?(host, guest)
# return :good_match
# end
# return :bad_match
return :good_match return :good_match
end end
@ -190,6 +175,7 @@ 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?
if host.housing_data['availability'][0] <= guest.arrival && if host.housing_data['availability'][0] <= guest.arrival &&
host.housing_data['availability'][1] >= guest.departure host.housing_data['availability'][1] >= guest.departure
return true return true
@ -208,4 +194,10 @@ module AdminHelper
end).html_safe end).html_safe
end).html_safe end).html_safe
end end
def admin_help_pages
return {
housing: :housing
}
end
end end

2
app/helpers/geocoder_helper.rb

@ -74,7 +74,7 @@ module GeocoderHelper
return nil unless city.present? return nil unless city.present?
hash = Hash.new hash = Hash.new
region_translation = region.present? && country.present? ? _("geography.subregions.#{country}.#{region}", locale: locale) : '' region_translation = region.present? && country.present? ? I18n.t("geography.subregions.#{country}.#{region}", locale: locale, resolve: false) : ''
country_translation = country.present? ? _("geography.countries.#{country}", locale: locale) : '' country_translation = country.present? ? _("geography.countries.#{country}", locale: locale) : ''
hash[:city] = _!(city) if city.present? hash[:city] = _!(city) if city.present?
hash[:region] = region_translation if region_translation.present? hash[:region] = region_translation if region_translation.present?

3
app/helpers/registration_helper.rb

@ -8,6 +8,7 @@ module RegistrationHelper
def registration_status(registration) def registration_status(registration)
return :unregistered if registration.nil? return :unregistered if registration.nil?
return :cancelled if registration.is_attending == 'n'
return registration.status return registration.status
end end
@ -47,7 +48,7 @@ module RegistrationHelper
completed_steps = registration.steps_completed || [] completed_steps = registration.steps_completed || []
last_step = nil last_step = nil
steps = current_registration_steps(registration) || [] steps = current_registration_steps(registration) || []
steps.each do | step | steps.each do |step|
# return the last enabled step if this one is disabled # return the last enabled step if this one is disabled
return last_step unless step[:enabled] return last_step unless step[:enabled]

67
app/helpers/table_helper.rb

@ -1,7 +1,12 @@
module TableHelper module TableHelper
def html_edit_table(excel_data, options = {}) def html_edit_table(excel_data, options = {})
attributes = { class: options[:class], id: options[:id] } attributes = { class: options[:class], id: options[:id] }
attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present? if options[:editable].present? || options[:sortable].present?
attributes[:data] = {
'update-url' => options[:editable],
'sort-url' => options[:sortable]
}
end
if options[:column_names].is_a? Hash if options[:column_names].is_a? Hash
return content_tag(:table, attributes) do return content_tag(:table, attributes) do
@ -9,11 +14,11 @@ module TableHelper
column_names = {} column_names = {}
(content_tag(:thead) do (content_tag(:thead) do
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? ? _(excel_data[:keys][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
if (options[:row_spans] || {})[column].present? if (options[:row_spans] || {})[column].present?
row_count += (options[:row_spans][column] - 1) row_count += (options[:row_spans][column] - 1)
@ -30,15 +35,18 @@ module TableHelper
for i in 0...max_columns for i in 0...max_columns
columns_html = '' columns_html = ''
column_names.each do | header_name, columns | column_names.each do |header_name, columns|
column = columns[i] column = columns[i]
if column.present? if column.present?
attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } } attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } }
if (options[:row_spans] || {})[column].present? if (options[:row_spans] || {})[column].present?
attributes[:rowspan] = options[:row_spans][column] attributes[:rowspan] = options[:row_spans][column]
end end
columns_html += content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '', rowspan: attributes[:rowspan]) +
edit_column(nil, column, nil, attributes, excel_data, options) column_text = excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : ''
columns_html += content_tag(:th, column_text.html_safe, rowspan: attributes[:rowspan]) +
edit_column(nil, column, nil, attributes, excel_data, options)
elsif column != false elsif column != false
columns_html += content_tag(:td, ' ', colspan: 2, class: :empty) columns_html += content_tag(:td, ' ', colspan: 2, class: :empty)
end end
@ -56,8 +64,9 @@ 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 } }
columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') + column_text = excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : ''
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
end end
end end
@ -70,7 +79,16 @@ module TableHelper
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] }
attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present?
if options[:editable].present?
attributes[:data] ||= {}
attributes[:data]['update-url'] = options[:editable]
end
if options[:sortable].present?
attributes[:data] ||= {}
attributes[:data]['sort-url'] = options[:sortable]
end
content_tag(:table, attributes) do content_tag(:table, attributes) do
(content_tag(:thead) do (content_tag(:thead) do
content_tag(:tr, excel_header_columns(excel_data)) content_tag(:tr, excel_header_columns(excel_data))
@ -106,7 +124,17 @@ 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
columns += content_tag(:th, data[:keys][column].present? ? _(data[:keys][column]) : '', class: class_name) column_text = data[:keys][column].present? ? _(data[:keys][column]) : ''
attrs = { class: class_name }
unless @sort_column.nil?
attrs[:data] = { colname: column }
if @sort_column == column
attrs[:data][:dir] = @sort_dir || :down
end
end
columns += content_tag(:th, column_text.html_safe, attrs)
end end
end end
@ -214,7 +242,7 @@ module TableHelper
def edit_column(row, column, value, attributes, data, options) def edit_column(row, column, value, attributes, data, options)
attributes[:class] << 'has-editor' attributes[:class] << 'has-editor'
raw_value = row.present? ? (row[:raw_values][column] || value) : nil raw_value = row.present? ? ((row[:raw_values] || {})[column] || value) : nil
if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present? if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present?
value = row[:html_values][column] value = row[:html_values][column]
@ -299,14 +327,16 @@ module TableHelper
] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym }, ] + User.AVAILABLE_LANGUAGES.map { |l| "language_#{l}".to_sym },
questions: [ questions: [
:registration_fees_paid, :registration_fees_paid,
:payment_currency,
:payment_method,
:is_attending, :is_attending,
:arrival, :arrival,
:departure, :departure,
:housing, :housing,
:bike, :bike,
:food, :food,
:group_ride,
:companion_email, :companion_email,
:allergies,
:other :other
], ],
hosting: [ hosting: [
@ -315,14 +345,15 @@ module TableHelper
:phone, :phone,
:first_day, :first_day,
:last_day :last_day
] + ConferenceRegistration.all_spaces + ] + ConferenceRegistration.all_spaces + [
ConferenceRegistration.all_considerations + [
:notes :notes
] ]
}, },
row_spans: { row_spans: {
allergies: 3, other: 2,
other: 2 address: 2,
city: 2,
notes: 4
}, },
required_columns: [:name, :email], required_columns: [:name, :email],
editable: administration_update_path(@this_conference.slug, @admin_step), editable: administration_update_path(@this_conference.slug, @admin_step),
@ -337,12 +368,15 @@ module TableHelper
primary_key: :id, primary_key: :id,
column_names: [ column_names: [
:registration_fees_paid, :registration_fees_paid,
:payment_currency,
:payment_method,
:is_attending, :is_attending,
:is_subscribed, :is_subscribed,
:city, :city,
:preferred_language, :preferred_language,
:arrival, :arrival,
:departure, :departure,
:group_ride,
:housing, :housing,
:bike, :bike,
:food, :food,
@ -360,6 +394,7 @@ module TableHelper
ConferenceRegistration.all_spaces + ConferenceRegistration.all_spaces +
ConferenceRegistration.all_considerations, ConferenceRegistration.all_considerations,
editable: administration_update_path(@this_conference.slug, @admin_step), editable: administration_update_path(@this_conference.slug, @admin_step),
sortable: administration_step_path(@this_conference.slug, @admin_step),
column_options: @column_options column_options: @column_options
} }
end end

155
app/helpers/widgets_helper.rb

@ -99,75 +99,86 @@ module WidgetsHelper
def host_guests_table(registration) def host_guests_table(registration)
id = registration.id id = registration.id
html = '' html = ''
first_row = true
@housing_data[id][:guests].each do |area, guests| @housing_data[id][:guests].each do |area, guests|
guest_rows = '' guest_rows = ''
guests.each do |guest_id, guest| space_size = (@housing_data[id][:space][area] || 0)
status_html = ''
@housing_data[id][:guest_data][guest_id][:errors].each do |error, value| if space_size > 0 || guests.size > 0
if value.is_a?(Array) guests.each do |guest_id, guest|
value.each do |v| status_html = ''
status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: v))
@housing_data[id][:guest_data][guest_id][:errors].each do |error, value|
if value.is_a?(Array)
value.each do |v|
status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: v))
end
else
status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: value))
end end
else
status_html += content_tag(:li, _("errors.messages.housing.space.#{error.to_s}", vars: value))
end end
end
@housing_data[id][:guest_data][guest_id][:warnings].each do |error, value| @housing_data[id][:guest_data][guest_id][:warnings].each do |error, value|
if value.is_a?(Array) if value.is_a?(Array)
value.each do |v| value.each do |v|
status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", v)) status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", v))
end
else
status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", vars: value))
end end
else end
status_html += content_tag(:li, _("warnings.messages.housing.space.#{error.to_s}", vars: value))
if status_html.present?
status_html = content_tag(:ul, status_html.html_safe)
end
guest_rows += content_tag :tr, id: "hosted-guest-#{guest_id}" do
(content_tag :td, guest[:guest].user.name) +
(content_tag :td do
(guest[:guest].from +
(content_tag :a, (_'actions.workshops.Remove'), href: '#', class: 'remove-guest', data: { guest: guest_id })).html_safe
end) +
(content_tag :td, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy])
end
end
# add empty rows to represent empty guest spots
for i in guests.size...space_size
guest_rows += content_tag :tr, class: 'empty-space' do
(content_tag :td, '&nbsp'.html_safe, colspan: 2) +
(content_tag :td)
end end
end end
status_html = ''
if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present?
@housing_data[id][:warnings][:space][area].each do |w|
status_html += content_tag(:li, _("warnings.messages.housing.space.#{w.to_s}"))
end
end
if status_html.present? if status_html.present?
status_html = content_tag(:ul, status_html.html_safe) status_html = content_tag(:ul, status_html.html_safe)
end end
guest_rows += content_tag :tr, id: "hosted-guest-#{guest_id}" do unless first_row
(content_tag :td, guest[:guest].user.name) + html += content_tag :tr, class: :spacer do
(content_tag :td do content_tag :td, '', colspan: 3
(guest[:guest].from + end
(content_tag :a, (_'actions.workshops.Remove'), href: '#', class: 'remove-guest', data: { guest: guest_id })).html_safe
end) +
(content_tag :td, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy])
end end
end
space_size = (@housing_data[id][:space][area] || 0) html += content_tag :tr do
(content_tag :th, (_"forms.labels.generic.#{area}"), colspan: 2) +
# add empty rows to represent empty guest spots (content_tag :th, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy])
for i in guests.size...space_size
guest_rows += content_tag :tr, class: 'empty-space' do
(content_tag :td, '&nbsp'.html_safe, colspan: 2) +
(content_tag :td)
end end
end html += guest_rows
html += content_tag :tr, class: 'place-guest' do
status_html = '' content_tag :td, class: guests.size >= space_size ? 'full' : nil, colspan: 3 do
if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? content_tag :a, (_"forms.actions.generic.place_guest_in.#{area}"), class: 'select-guest button small', href: '#', data: { host: id, space: area }
@housing_data[id][:warnings][:space][area].each do |w| end
status_html += content_tag(:li, _("warnings.messages.housing.space.#{w.to_s}"))
end end
end
if status_html.present?
status_html = content_tag(:ul, status_html.html_safe)
end
html += content_tag :tr do first_row = false
(content_tag :th, (_"forms.labels.generic.#{area}"), colspan: 2) +
(content_tag :th, status_html.html_safe, class: [:state, status_html.present? ? :unhappy : :happy])
end
html += guest_rows
html += content_tag :tr, class: 'place-guest' do
content_tag :td, class: guests.size >= space_size ? 'full' : nil, colspan: 3 do
content_tag :a, (_'forms.actions.generic.place_guest'), class: 'select-guest', href: '#', data: { host: id, space: area }
end
end end
end end
@ -181,21 +192,31 @@ module WidgetsHelper
id = registration.id id = registration.id
@housing_data[id][:guests].each do |area, guests| @housing_data[id][:guests].each do |area, guests|
max_space = @housing_data[id][:space][area] || 0 max_space = @housing_data[id][:space][area] || 0
area_name = (_"forms.labels.generic.#{area}")
status_html = '' # don't include the area if the host doesn't want anyone there
if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present? if max_space > 0 || guests.size > 0
@housing_data[id][:warnings][:space][area].each do |w| area_name = (_"forms.labels.generic.#{area}")
status_html += content_tag(:div, _("warnings.housing.space.#{w.to_s}"), class: 'warning') status_html = ''
if @housing_data[id][:warnings].present? && @housing_data[id][:warnings][:space].present? && @housing_data[id][:warnings][:space][area].present?
@housing_data[id][:warnings][:space][area].each do |w|
status_html += content_tag(:div, _("warnings.housing.space.#{w.to_s}"), class: 'warning')
end
end end
space_html = content_tag(:h5, area_name + _!(" (#{guests.size.to_s}/#{max_space.to_s})") + status_html.html_safe)
guest_items = ''
guests.each do |guest_id, guest|
guest_items += content_tag(:li, guest[:guest].user.name, id: "hosted-guest-#{guest_id}")
end
space_html += content_tag(:ul, guest_items.html_safe)
# see if the space is overbooked
booked_state = guests.size >= max_space ? (guests.size > max_space ? :overbooked : :booked) : nil
# let space be overbooked, even bed space can be overbooked if a couple is staying in the bed
space_html += button :place_guest, type: :button, value: "#{area}:#{id}", class: [:small, 'place-guest', 'on-top-only', booked_state, max_space > 0 ? nil : :unwanted]
html += content_tag(:div, space_html, class: [:space, area, max_space > 0 || guests.size > 0 ? nil : 'on-top-only'])
end end
space_html = content_tag(:h5, area_name + _!(" (#{guests.size.to_s}/#{max_space.to_s})") + status_html.html_safe)
guest_items = ''
guests.each do |guest_id, guest|
guest_items += content_tag(:li, guest[:guest].user.name, id: "hosted-guest-#{guest_id}")
end
space_html += content_tag(:ul, guest_items.html_safe)
space_html += button :place_guest, type: :button, value: "#{area}:#{id}", class: [:small, 'place-guest', 'on-top-only', guests.size >= max_space ? (guests.size > max_space ? :overbooked : :booked) : nil, max_space > 0 ? nil : :unwanted]
html += content_tag(:div, space_html, class: [:space, area, max_space > 0 || guests.size > 0 ? nil : 'on-top-only'])
end end
classes << 'status-warning' if @housing_data[id][:warnings].present? classes << 'status-warning' if @housing_data[id][:warnings].present?
@ -228,6 +249,16 @@ module WidgetsHelper
end end
end end
def link_help_dlg(topic, args = {})
@help_dlg ||= true
args[:data] ||= {}
args[:data]['info-title'] = I18n.t("help.headings.#{topic}")
args[:data]['help-text'] = true
content_tag(:a, args) do
(I18n.t('help.link_text') + content_tag(:template, (render "/help/#{topic}"), class: 'message')).html_safe
end
end
def button_with_confirmation(button_name, confirmation_text = nil, args = {}) def button_with_confirmation(button_name, confirmation_text = nil, args = {})
if confirmation_text.is_a? Hash if confirmation_text.is_a? Hash
args = confirmation_text args = confirmation_text

4
app/views/application/user_settings.html.haml

@ -7,10 +7,10 @@
- if @conference.present? && (@conference.registration_status == :pre || @conference.registration_status == :open) - if @conference.present? && (@conference.registration_status == :pre || @conference.registration_status == :open)
%p=_'articles.user_settings.paragraphs.conference_registration', :t %p=_'articles.user_settings.paragraphs.conference_registration', :t
= link_to (_'actions.conference.edit_registration'), register_path(@conference.slug), class: :button = link_to (_'actions.conference.edit_registration'), register_path(@conference.slug), class: :button
- if @conferences.present? - if @my_conferences.present?
%h3=_'articles.user_settings.headings.Your_Conferences' %h3=_'articles.user_settings.headings.Your_Conferences'
.link-dump .link-dump
- @conferences.each do | conference | - @my_conferences.each do |conference|
= link_to (_!conference.title), administrate_conference_path(conference.slug), class: :button = link_to (_!conference.title), administrate_conference_path(conference.slug), class: :button
= form_tag update_settings_path do = form_tag update_settings_path do

13
app/views/conference_administration/_hosts_table.html.haml

@ -1,11 +1,20 @@
.guests-housed .guests-housed
%h5 Guests Housed: %h5 Guests Housed:
.data="#{@guests_housed} / #{@guests.size}" .data{class: @guests_housed < @guests.size ? :unhappy : :happy }="#{@guests_housed} / #{@guests.size}"
- if @guests_housed > 0
.guests-housed
%h5 Unhappy hosts and guests:
.data{class: @unhappy_people.size > 0 ? :unhappy : :happy }="#{@unhappy_people.size}"
- first_row = true
%table.hosts.admin-edit %table.hosts.admin-edit
- @hosts.each do | id, registration | - @hosts.each do |id, registration|
- unless first_row
%tr.spacer
%td
%tr.host %tr.host
%th %th
.name=registration.user.name .name=registration.user.name
.address=registration.housing_data['address'] .address=registration.housing_data['address']
%td.inner-table{colspan: 2}=host_guests_table(registration) %td.inner-table{colspan: 2}=host_guests_table(registration)
- first_row = false

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

@ -6,5 +6,5 @@
= admin_update_form class: 'guest-dlg', id: 'guest-list-table' do = admin_update_form class: 'guest-dlg', id: 'guest-list-table' do
%h3 Select a Guest %h3 Select a Guest
#table #table
.actions .actions.center
= link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, @admin_step, :format => :xlsx), class: [:button, :download] = link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, @admin_step, :format => :xlsx), class: [:button, :download]

8
app/views/conference_administration/_registrations.html.haml

@ -6,12 +6,12 @@
%a.button{data: { expands: 'registrations-table' }}='expand' %a.button{data: { expands: 'registrations-table' }}='expand'
%a.button.delete{data: { contracts: 'registrations-table' }}='close' %a.button.delete{data: { contracts: 'registrations-table' }}='close'
%a.button.modify{data: { 'opens-modal': 'new-registration' }}='+' %a.button.modify{data: { 'opens-modal': 'new-registration' }}='+'
.table-scroller .table-scroller#registrations
= html_table @excel_data, registrations_table_options = html_table(@excel_data, registrations_table_options)
= admin_update_form id: 'new-registration', class: 'modal-edit' do = admin_update_form id: 'new-registration', class: 'modal-edit' do
.modal-edit-overlay{data: { 'closes-modal': 'new-registration' }} .modal-edit-overlay{data: { 'closes-modal': 'new-registration' }}
.modal-edit-content .modal-edit-content
= html_edit_table @excel_data, registrations_edit_table_options = html_edit_table @excel_data, registrations_edit_table_options
.actions.right .actions.center
%a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel' %a.button.subdued{data: { 'closes-modal': 'new-registration' }} Cancel
= button :save, value: :save, class: :modify = button :save, value: :save, class: :modify

102
app/views/conference_administration/_select_guest_table.html.haml

@ -1,50 +1,54 @@
= hidden_field_tag :host, host.id .host
.host-field = hidden_field_tag :host, host.id
%h4.inline=_'forms.labels.generic.name' %h4 Host
%span.plain-value= host.user.name .table-scroller.no-edit
.host-field %table.guests.admin-edit
%h4.inline=_'articles.conference_registration.headings.host.availability' %tr
%span.plain-value= host.housing_data['availability'].present? && host.housing_data['availability'][1].present? ? date_span(host.housing_data['availability'][0].to_date, host.housing_data['availability'][1].to_date) : '' %th.corner
- if host.housing_data['considerations'].present? %th=_'forms.labels.generic.hosting_space'
.host-field %th=_'forms.labels.generic.first_day'
%h4.inline=_'articles.conference_registration.headings.host.considerations' %th=_'forms.labels.generic.last_day'
%span.plain-value= (host.housing_data['considerations'].map { | consideration | _"articles.conference_registration.host.considerations.#{consideration}" }).join(', ') %th=_'articles.conference_registration.headings.host.notes'
- if sanitize(host.housing_data['notes'], tags: []).present? %tr
.host-field - availability = host.housing_data['availability']
%h4=_'articles.conference_registration.headings.host.notes' %th=host.user.name
%blockquote= host.housing_data['notes'].html_safe %td=_"forms.labels.generic.#{space}"
%table.guests.admin-edit %td=(availability || [])[0].present? ? availability[0].present? ? date(availability[0].to_date, :span_same_year_date_1) : '' : ''
%tr %td=(availability || [])[1].present? ? date(availability[1].to_date, :span_same_year_date_1) : ''
%th.corner %td=((host.housing_data || {})['notes'] || '').html_safe
%th=_'forms.labels.generic.organization'
%th=_'forms.labels.generic.city'
%th=_'forms.labels.generic.housing'
%th=_'articles.admin.housing.headings.arrival_departure'
%th=_'articles.conference_registration.headings.companion'
%th=_'forms.labels.generic.food'
%th=_'forms.labels.generic.other'
- @guests.each do | id, registration |
%tr.selectable{class: get_housing_match(host, registration, space).to_s.gsub('_', '-'), data: {host: host.id, guest: id, space: space}}
%th=registration.user.name
%td
- if registration.user.organizations.present?
= registration.user.organizations.first.name
- else
%em None
%td=registration.city
%td=registration.housing.present? ? (_"articles.conference_registration.questions.housing.#{registration.housing}") : ''
%td=date_span(registration.arrival.to_date, registration.departure.to_date)
- companion = companion(registration)
%td=companion.present? ? (companion.is_a?(User) ? companion.named_email : (_"articles.conference_registration.terms.registration_status.#{companion}")) : ''
%td=registration.food.present? ? (_"articles.conference_registration.questions.food.#{registration.food}") : ''
%td
.p=registration.other
.legend %h4 Guests
%h4 Legend .guest-table
%ul .table-scroller.no-edit
%li.good-match Good Match %table.guests.admin-edit
%li.bad-match Poor Match %tr
%li.selected-space Also in this space %th.corner
%li.other-space Also with this host %th=_'forms.labels.generic.housing'
%li.other-host Already hosted %th=_'forms.labels.generic.arrival'
%th=_'forms.labels.generic.departure'
%th=_'forms.labels.generic.other_notes'
%th=_'forms.labels.generic.city'
%th=_'forms.labels.generic.organization'
%th=_'articles.conference_registration.headings.companion'
%th=_'forms.labels.generic.food'
- @guests.each do |id, registration|
%tr.selectable{class: get_housing_match(host, registration, space).to_s.gsub('_', '-'), data: {host: host.id, guest: id, space: space}}
%th.break-ok=registration.user.name
%td=registration.housing.present? ? (_"articles.conference_registration.questions.housing.#{registration.housing}") : ''
%td
- if registration.arrival.present?
=date(registration.arrival.to_date, :span_same_year_date_1)
%td
- if registration.departure.present?
=date(registration.departure.to_date, :span_same_year_date_1)
%td.break-ok
.p=[registration.allergies, registration.other, (registration.housing_data || {})['other']].compact.join("\n\n")
%td=registration.city
%td.break-ok
- if registration.user.organizations.present?
= registration.user.organizations.first.name
- else
%em None
- companion = companion(registration)
%td=companion.present? ? (companion.is_a?(User) ? companion.named_email : (_"articles.conference_registration.terms.registration_status.#{companion}")) : ''
%td=registration.food.present? ? (_"articles.conference_registration.questions.food.#{registration.food}") : ''

6
app/views/conference_administration/administration.html.haml

@ -1,7 +1,7 @@
- body_class 'banner-bottom' unless @this_conference.poster.present? - body_class 'banner-bottom' unless @this_conference.poster.present?
- add_stylesheet :admin - add_stylesheet :admin
- content_for :banner do - content_for :banner do
= render :partial => 'application/header', :locals => { page_group: :administration, page_key: 'Administration', image_file: @this_conference.poster_url || 'admin.jpg'} = render partial: 'application/header', locals: { page_group: :administration, page_key: 'Administration', image_file: @this_conference.poster_url || 'admin.jpg'}
%article %article
= row do = row do
@ -10,7 +10,7 @@
%h2=@this_conference.title %h2=@this_conference.title
%p=_'articles.admin.paragraphs.administration', :p %p=_'articles.admin.paragraphs.administration', :p
%ul.break %ul.break
- administration_steps.each do | step, actions | - administration_steps.each do |step, actions|
%li %li
.info .info
%h3=_"articles.admin.#{step}.heading", :t %h3=_"articles.admin.#{step}.heading", :t
@ -18,7 +18,7 @@
%p=_"articles.admin.#{step}.description", :p %p=_"articles.admin.#{step}.description", :p
.actions.figures .actions.figures
- actions.each do | action | - actions.each do |action|
- action_text = (_"articles.admin.#{step}.headings.#{action}", :t) - action_text = (_"articles.admin.#{step}.headings.#{action}", :t)
.figure .figure
= link_to administration_step_path(@this_conference.slug, action.to_s) do = link_to administration_step_path(@this_conference.slug, action.to_s) do

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

@ -1,11 +1,13 @@
- body_class 'banner-bottom' unless @this_conference.poster.present? - body_class 'banner-bottom' unless @this_conference.poster.present?
- add_stylesheet :admin - add_stylesheet :admin
- content_for :banner do - content_for :banner do
= render :partial => 'application/header', :locals => { page_group: :administration, page_key: 'Administration', image_file: @this_conference.poster_url || 'admin.jpg'} = render partial: 'application/header', locals: { page_group: :administration, page_key: 'Administration', image_file: @this_conference.poster_url || 'admin.jpg'}
%article{id: "admin-#{@admin_step}"} %article{id: "admin-#{@admin_step}"}
= row do = row do
= columns(medium: 12) do = columns(medium: 12) do
- if admin_help_pages[@admin_step.to_sym]
= link_help_dlg("admin_#{admin_help_pages[@admin_step.to_sym]}", class: ['button', 'help-link'])
%h2.floating=(_"articles.admin.#{@admin_group}.headings.#{@admin_step}", :t) %h2.floating=(_"articles.admin.#{@admin_group}.headings.#{@admin_step}", :t)
= row do = row do
= columns(medium: 12) do = columns(medium: 12) do

14
app/views/conferences/index.html.haml

@ -1,14 +0,0 @@
- page_name = 'All '+(@conference_type ? @conference_type.title+' ' : '')+' Conferences'
- title page_name
- banner_image '/assets/conference.jpg'
- page_style :list
- content_for :banner do
.row
.columns
%h1=_'page.Conferences'
%h2=page_name
%ul.small-block-grid-1.medium-block-grid-2.large-block-grid-3.conference-list.preview-list
- @conferences.each do |conference|
%li=render 'preview', :conference => conference

31
app/views/help/_admin_housing.html.haml

@ -0,0 +1,31 @@
%p On this page you will see a list of hosts (housing providers) each with a list of their open spaces, beds, floor space, and tent space.
%h3 How to match a guest with host
%p Find a host and select a space to place a guest in, then press the "Place a guest in..." button to add a guest to that area. Once you press that button a popup will appear showing you extended details on the host and a list of guests with details. Take a look at the notes section for both the host and the guest to determine if the two will make a good match but also pay attention to the colour of the row:
.legend
%h4 Legend
%ul
%li.good-match Good Match
%li.bad-match Poor Match
%li.selected-space Also in this space
%li.other-space Also with this host
%li.other-host Already hosted
%h3 Dealing with poor matches
%p A poor match can happen for several reasons:
%ul
%li A host space may not be available for the entire time that a guest wishes to stay
%li A guest may be placed in a space (bed, floor, or tent) which is not their preferred space
%li A guest may be placed in a space without their companion
%li A host may have more guests than they indicated as their maximum
%p When a match is likely good, you will see a &#x1F601; beside their placement, when a match is poor you will see a &#x1F621;. If you see the unhappy face, you can hover over the face to show more details on why the guest or host is unhappy.
%p You may wish to remove the guest and fin a better placement but keep in mind that this will not always be possible. For example, often guests will ask to be placed with many other friends but arranging this can be difficult. Speak with guests and hosts if possible to arrange a compromise, it is generally encouraged that guests stay with new people as long as they feel safe.
%h3 Notifying hosts and guests of their placements
%p At the moment there is no automated tool to do this for you. You will need to download the excel speadsheet at the bottom of the page to get all placements and their contact info.

7
app/views/layouts/application.html.haml

@ -61,6 +61,13 @@
.dlg-inner .dlg-inner
%p.message='' %p.message=''
%button.close=_'modals.done_button' %button.close=_'modals.done_button'
- if @help_dlg.present?
.dlg#help-dlg{data: { nofocus: 1 }}
.dlg-content
%h2.title=_'modals.help'
.dlg-inner
.message=''
%button.close=_'modals.done_button'
- if @login_dlg.present? - if @login_dlg.present?
.dlg#login-dlg .dlg#login-dlg
.dlg-content .dlg-content

15
config/assets_cdn.yml

@ -1,15 +0,0 @@
development:
enabled: false
host: bikebike.org
preview:
enabled: false
host: preview-cdn.bikebike.org
protocol: https
fallback_protocol: http
production:
enabled: true
host: cdn.bikebike.org
protocol: https
fallback_protocol: http

2
config/environments/development.rb

@ -44,7 +44,7 @@ BikeBike::Application.configure do
# enable_starttls_auto: true, # enable_starttls_auto: true,
# openssl_verify_mode: 'none', # openssl_verify_mode: 'none',
# user_name: 'info@bikebike.org', # user_name: 'info@bikebike.org',
# password: config.app_config['email_password'] # password: 'Toronto@)!)'
# } # }
config.action_mailer.raise_delivery_errors = true config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = true config.action_mailer.perform_deliveries = true

34
config/locales/en.yml

@ -1331,6 +1331,10 @@ en:
conference: conference:
actions: actions:
Register: Register Register: Register
help:
link_text: Help
headings:
admin_housing: Help for Housing
modals: modals:
confirm: Please Confirm confirm: Please Confirm
yes_button: 'Yes' yes_button: 'Yes'
@ -1461,10 +1465,9 @@ en:
instead of a guest form. If you want to consider surrounding cities as instead of a guest form. If you want to consider surrounding cities as
well, enter the distance from %{city} that you wish to be included as well, enter the distance from %{city} that you wish to be included as
providers. Cities are measured from center to center. providers. Cities are measured from center to center.
housing: Pair each housing provider with a list of guests. Try to match housing: Match guests with hosts, and fulfill their housing
guests with hosts and other guests who are good matches and respect their requests to the best of your ability. Use guest notes and the face icons to determine if a guest is a good match for the host.
requests, but also keep in mind that we cannot always respect all requests Hover over unhappy faces for details on how to find the guest a better match. Keep in mind that you may not be able to accommodate every request.
and part of the Bike!Bike! experience is getting to know new people.
locations: locations:
heading: Locations heading: Locations
description: Locations are used to schedule workshops, events, and meals. description: Locations are used to schedule workshops, events, and meals.
@ -2079,6 +2082,7 @@ en:
quiet: Quiet household quiet: Quiet household
pets: House has dogs or cats pets: House has dogs or cats
can_provide_housing: I can provide housing. can_provide_housing: I can provide housing.
housing_provider: Host?
not_attending: I will not be attending the conference not_attending: I will not be attending the conference
questions: questions:
bike: bike:
@ -2096,6 +2100,10 @@ en:
house: Indoor Location house: Indoor Location
none: I'll take care of it none: I'll take care of it
tent: Tent Space tent: Tent Space
housing_short:
house: House
none: None
tent: Tent
bikes: bikes:
medium: Medium medium: Medium
none: "(none)" none: "(none)"
@ -2116,9 +2124,10 @@ en:
unregistered: Unregistered unregistered: Unregistered
preregistered: Preregistered preregistered: Preregistered
registered: Registered registered: Registered
cancelled: Cancelled
companion: Companion companion: Companion
companion_email: Companion Email companion_email: Companion Email
Preferred_Languages: Preferred Language Preferred_Languages: Language
is_attending: Attending? is_attending: Attending?
about_bikebike: about_bikebike:
paragraphs: paragraphs:
@ -2282,6 +2291,8 @@ en:
paypal: Online paypal: Online
on_arrival: In person on_arrival: In person
none: None none: None
payment_method: Payment Method
payment_currency: Payment Currency
space: Available Space space: Available Space
hosting_dates: When will you be in town? hosting_dates: When will you be in town?
type: Type type: Type
@ -2295,8 +2306,8 @@ en:
other: Disabilities, housing preferences, etc. other: Disabilities, housing preferences, etc.
email: Email address email: Email address
allergies: Allergies allergies: Allergies
arrival: Arrival date arrival: Arrival
departure: Departure date departure: Departure
location: City, State/Province, Country location: City, State/Province, Country
name: Name name: Name
subject: Subject subject: Subject
@ -2306,10 +2317,13 @@ en:
notes: Notes notes: Notes
message: 'Your Message:' message: 'Your Message:'
address: Street Address address: Street Address
address_short: Address
phone: Phone number phone: Phone number
phone_short: Phone
bed_space: Bed/Couch Space bed_space: Bed/Couch Space
floor_space: Floor Space floor_space: Floor Space
tent_space: Tent Space tent_space: Tent Space
hosting_space: Space
first_day: From first_day: From
last_day: To last_day: To
body: Body body: Body
@ -2329,7 +2343,7 @@ en:
closed: Closed closed: Closed
open: Open open: Open
pre: Pre-Registration pre: Pre-Registration
registration_status: Registration Status registration_status: Status
companion: Email address companion: Email address
block_number: Block block_number: Block
days: Days days: Days
@ -2395,6 +2409,10 @@ en:
create: Create create: Create
delete: Delete delete: Delete
place_guest: Place Guest place_guest: Place Guest
place_guest_in:
bed_space: Place a guest in a bed or couch
floor_space: Place a guest on the floor
tent_space: Place a guest in a tent space
set_host: Set Host set_host: Set Host
add_comment: Add Comment add_comment: Add Comment
reply: Reply reply: Reply

Loading…
Cancel
Save