diff --git a/Gemfile.lock b/Gemfile.lock index 1d600ad59a4c0e062df02aa07fcb99d31693b549..366c239e51f1a5586900221c55a7743bfb9cfc14 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -149,8 +149,5 @@ DEPENDENCIES thin unicorn -RUBY VERSION - ruby 2.2.3p173 - BUNDLED WITH 1.13.0 diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 4dc62bebd2481ba1f350fe8d9f3c686bbb9f8646..0000000000000000000000000000000000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -module.exports = function (grunt) { - // load all grunt tasks - grunt.loadNpmTasks('grunt-contrib-less'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-cssmin'); - - grunt.initConfig({ - watch: { - // if any .less file changes in directory "public/css/" run the "less"-task. - css: { - files: "src/less/*.less", - tasks: ["less", "cssmin"] - } - }, - // "less"-task configuration - less: { - // production config is also available - development: { - options: { - // Specifies directories to scan for @import directives when parsing. - // Default value is the directory of the source, which is probably what you want. - paths: ["public/css/"], - }, - files: { - // compilation.css : source.less - "public/css/resource_scheduler.css": "src/less/resource_scheduler.less" - } - }, - }, - cssmin: { - target: { - files: [{ - expand: true, - src: ['public/css/resource_scheduler.css', 'public/css/resource_scheduler.css'], - }] - } - } - }); - // the default task (running "grunt" in console) is "watch" - grunt.registerTask('default', ['watch']); -}; \ No newline at end of file diff --git a/README.md b/README.md index eb67cb7e888bedea2343bab7bdcd633e9982d1b3..a977034ce502ef8d763a554aa394ed9ae11164e4 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Using local resources 8. Install the WDN Framework into the `public/wdn` directory...see [WDN Documentation](http://wdn.unl.edu/documentation). 9. Start the server by going to the root directory and doing `bundle exec shotgun -o 0.0.0.0 -p 9292`. This launches the server on localhost port 9292, listening everywhere (you can use your iimlemburg.unl.edu or whichever), and the server will automatically update to new code. If you add gems to the bundle, you will need to re-execute this command. 10. Navigate to `localhost:9292/` or similar and begin! +11. In another terminal, type `bundle exec guard` in the project root to execute LESS compilation. Quick Tutorial ============== diff --git a/db/migrate/20161018150000_add_recurring_reservation_tag.rb b/db/migrate/20161018150000_add_recurring_reservation_tag.rb new file mode 100644 index 0000000000000000000000000000000000000000..d20aa7d84f18ec52b3a87bee828bcd7883b04aaa --- /dev/null +++ b/db/migrate/20161018150000_add_recurring_reservation_tag.rb @@ -0,0 +1,7 @@ +require 'active_record' + +class AddRecurringReservationTag < ActiveRecord::Migration + def change + add_column :reservations, :recurring_reference_id, :integer + end +end \ No newline at end of file diff --git a/routes/resources.rb b/routes/resources.rb index 3668be06ea50418cbd1ed77b61c5974c4ab1107b..98f83ea9b8632ec8cdc3705832129608695299cd 100644 --- a/routes/resources.rb +++ b/routes/resources.rb @@ -219,6 +219,7 @@ post '/:service_space_url_name/resources/:resource_id/reserve/?' do end end + recurring_reference_id = (Reservation.maximum(:recurring_reference_id) || 0) + 1 if params.checked?('recurring') begin recurs_until_date = Time.strptime(params[:recurs_until_date], '%m/%d/%Y').midnight.in_time_zone @@ -406,7 +407,8 @@ post '/:service_space_url_name/resources/:resource_id/reserve/?' do :end_time => new_end, :is_training => false, :user_id => @user.id, - :title => params[:title] + :title => params[:title], + :recurring_reference_id => recurring_reference_id ) successful += 1 end @@ -416,14 +418,13 @@ post '/:service_space_url_name/resources/:resource_id/reserve/?' do unless messages.empty? if params[:recurring_type] == 'daily' flash :alert, 'Some recurring reservations were not created', "#{messages.count} recurring reservations were not made. This may be just because the space is closed for certain days." - puts messages else flash :alert, 'Some recurring reservations were not created', "<ul><li>#{messages.join('</li><li>')}</li></ul>" end end end - Reservation.create( + res = Reservation.create( :resource_id => resource.id, :event_id => nil, :start_time => start_time, @@ -432,6 +433,10 @@ post '/:service_space_url_name/resources/:resource_id/reserve/?' do :user_id => @user.id, :title => params[:title] ) + if params.checked?('recurring') + res.recurring_reference_id = recurring_reference_id + res.save + end flash(:success, 'Reservation Created', "You have successfully reserved #{resource.name} for #{params[:length]} minutes at #{start_time.in_time_zone.strftime('%A, %B %d at %l:%M %P')}") redirect "/#{@space.url_name}/resources/#{resource.id}/calendar/#{params[:kiosk_mode] ? '?kiosk_mode=true' : ''}" @@ -628,4 +633,29 @@ post '/:service_space_url_name/resources/:resource_id/cancel/:reservation_id/?' redirect back end +post '/:service_space_url_name/resources/:resource_id/cancel_all/:recurring_reference_id/?' do + require_login + load_service_space + + # check that the user requesting cancel is the same as the one on the reservation + reservations = Reservation.where(:recurring_reference_id => params[:recurring_reference_id]).all + count = reservations.count + if reservations.empty? + flash :alert, 'Not Found', 'Those reservations were not found.' + redirect back + end + + reservations.each do |reservation| + if reservation.user_id != @user.id + flash :alert, 'Unauthorized', 'That is not your reservation.' + redirect back + end + end + + Reservation.where(:recurring_reference_id => params[:recurring_reference_id]).delete_all + + flash :success, 'Reservations Cancelled', "#{count} reservations have been removed." + redirect back +end + diff --git a/views/calendar.erb b/views/calendar.erb index 75cb722709005b07b8f3e77aaac31fd166b018c0..3103b0ad8dc19407a00053138abdaa35481964aa 100644 --- a/views/calendar.erb +++ b/views/calendar.erb @@ -1,6 +1,11 @@ <% events_groups = events.group_by do |event| event.start_time.in_time_zone.strftime("%Y/%m/%d") -end %> +end + +HALF_HOUR_HEIGHT = 28 # pixel height of half-hour sections in calendar. DO NOT CHANGE RESPONSIVELY +SIX_AM_MINUTES = 360 # start time of calendar +EIGHT_PM_MINUTES = 1200 # end time of calendar +%> <div id="pagetitle"> <h3> @@ -73,18 +78,22 @@ end %> closeds.each do |closed| start_time = closed[0] %> <% end_time = closed[1] %> - <% - if [((end_time - 360) / 30).floor, 35].min < 0 + <% + # if the end time is before 6 am, skip. + if ((end_time - SIX_AM_MINUTES) / 30).floor < 0 next end - top = ((start_time - 360) / 30) * 20 - height = (end_time - start_time) * 20 / 30 + # calculate top based off of 6 am start + top = ((start_time - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = (end_time - start_time) * HALF_HOUR_HEIGHT / 30 + # normalize top to 0, reduce height to accomodate if top < 0 height += top top = 0 - end - if top + height > 720 - height = 720 - top + end + # normalize the bottom to the bottom of the calendar at 8pm. + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top end %> <div class="status closed" title="Closed" style="top: <%= top %>px; height: <%= height %>px;"> @@ -97,17 +106,17 @@ end %> <% if record[:status] != 'open' && record[:status] != 'closed' %> <% start_time = record[:start] %> <% end_time = record[:end] %> - <% if [((end_time - 360) / 30).floor, 35].min < 0 + <% if ((end_time - SIX_AM_MINUTES) / 30).floor < 0 next end - top = ((start_time - 360) / 30) * 20 - height = (end_time - start_time) * 20 / 30 + top = ((start_time - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = (end_time - start_time) * HALF_HOUR_HEIGHT / 30 if top < 0 height += top top = 0 end - if top + height > 720 - height = 720 - top + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top end %> <div title="<%= record[:status].split('_').join(' ').capitalize_all %>" class="status <%= record[:status].downcase.split('_').join('-') %>" style="top: <%= top %>px; height: <%= height %>px;"> @@ -120,8 +129,8 @@ end %> <% day_events = events_groups[day.strftime("%Y/%m/%d")] %> <% unless day_events.nil? %> <% day_events.sort{|a, b| a.start_time <=> b.start_time}.each do |res| %> - <% start_slot = [(((res.start_time.in_time_zone - day.midnight) / 60 - 360) / 30).floor, 0].max - end_slot = [(((res.end_time.in_time_zone - day.midnight) / 60 - 360) / 30).floor, 35].min + <% start_slot = [(((res.start_time.in_time_zone - day.midnight) / 60 - SIX_AM_MINUTES) / 30).floor, 0].max + end_slot = [(((res.end_time.in_time_zone - day.midnight) / 60 - SIX_AM_MINUTES) / 30).floor, 35].min if end_slot < 0 next end @@ -130,8 +139,8 @@ end %> over = slots[k] if slots[k] > over end over = [over,3].min - top = (((res.start_time.in_time_zone - day.midnight) / 60 - 360) / 30) * 28 - height = res.length * 28 / 30 + top = (((res.start_time.in_time_zone - day.midnight) / 60 - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = res.length * HALF_HOUR_HEIGHT / 30 top_overflow = false bottom_overflow = false if top < 0 @@ -139,8 +148,8 @@ end %> top = 0 top_overflow = true end - if top + height > 720 - height = 720 - top + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top bottom_overflow = true events_groups[(day + 1.day).strftime("%Y/%m/%d")] ||= [] events_groups[(day + 1.day).strftime("%Y/%m/%d")] << res diff --git a/views/reserve.erb b/views/reserve.erb index 001f8ebc41a49817d93cc7205a98277fdecbb08c..a26f5ce4fe977e450809957704995b05f674446c 100644 --- a/views/reserve.erb +++ b/views/reserve.erb @@ -12,7 +12,12 @@ unless reservation.nil? end start_hour += 12 if start_hour == 0 start_minute = reservation.start_time.in_time_zone.min -end %> +end + +HALF_HOUR_HEIGHT = 28 # pixel height of half-hour sections in calendar. DO NOT CHANGE RESPONSIVELY +SIX_AM_MINUTES = 360 # start time of calendar +EIGHT_PM_MINUTES = 1200 # end time of calendar +%> <div id="pagetitle"> <h3><%= reservation.nil? ? 'Reserve Time for ' : 'Edit Reservation for ' %><%= resource.name %></h3> @@ -26,7 +31,7 @@ end %> <label class="day-header"><%= day.strftime("%^a %-m/%d") %></label> <div class="time-labels"> <div class="time-chart"> - <% (12..47).each do |j| %> + <% (12..39).each do |j| %> <div class="calendar-half-hour"> <label><%= "#{(j / 2) % 12 + (j==24?12:0)} #{j>=24?'PM':'AM'}" if j % 2 == 0 %></label> </div> @@ -41,8 +46,8 @@ end %> if end_slot < 0 next end - top = (((res.start_time.in_time_zone - day.midnight) / 60 - 360) / 30) * 28 - height = res.length * 28 / 30 + top = (((res.start_time.in_time_zone - day.midnight) / 60 - 360) / 30) * HALF_HOUR_HEIGHT + height = res.length * HALF_HOUR_HEIGHT / 30 %> <div class="event-container" style="top: <%= top %>px;"> <div class="reservation <%= 'editing' if !reservation.nil? && reservation.id == res.id %>" @@ -66,7 +71,7 @@ end %> ends = space_hour.hours.map{|record| record[:end]} %> <% closeds = [] - (360..1439).each do |j| + (360..1199).each do |j| if starts.include?(j) closed_end = j closeds << [closed_start, closed_end] @@ -77,24 +82,28 @@ end %> closed_start = j end end - closed_end = 1440 + closed_end = 1200 closeds << [closed_start, closed_end] closeds.each do |closed| start_time = closed[0] %> <% end_time = closed[1] %> - <% - if [((end_time - 360) / 30).floor, 35].min < 0 + <% + # if the end time is before 6 am, skip. + if ((end_time - SIX_AM_MINUTES) / 30).floor < 0 next end - top = ((start_time - 360) / 30) * 20 - height = (end_time - start_time) * 20 / 30 + # calculate top based off of 6 am start + top = ((start_time - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = (end_time - start_time) * HALF_HOUR_HEIGHT / 30 + # normalize top to 0, reduce height to accomodate if top < 0 height += top top = 0 - end - if top + height > 720 - height = 720 - top + end + # normalize the bottom to the bottom of the calendar at 8pm. + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top end %> <div class="status closed" title="Closed" style="top: <%= top %>px; height: <%= height %>px;"> @@ -106,18 +115,18 @@ end %> <% if record[:status] != 'open' && record[:status] != 'closed' %> <% start_time = record[:start] %> <% end_time = record[:end] %> - <% if [((end_time - 360) / 30).floor, 35].min < 0 + <% if [((end_time - SIX_AM_MINUTES) / 30).floor, 35].min < 0 next end - top = ((start_time - 360) / 30) * 20 - height = (end_time - start_time) * 20 / 30 + top = ((start_time - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = (end_time - start_time) * HALF_HOUR_HEIGHT / 30 if top < 0 height += top top = 0 end - if top + height > 720 - height = 720 - top - end + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top + end %> <div title="<%= record[:status].split('_').join(' ').capitalize_all %>" class="status <%= record[:status].downcase.split('_').join('-') %>" style="top: <%= top %>px; height: <%= height %>px;"> @@ -126,7 +135,7 @@ end %> <% end %> <% end %> <div> - <% (12..47).each do |j| %> + <% (12..39).each do |j| %> <div class="calendar-half-hour"> </div> diff --git a/views/resource_calendar.erb b/views/resource_calendar.erb index c55f35abc662a072737e63988983c4026cffaa2b..920a52699b2555735647b0aa2b044510a67679d8 100644 --- a/views/resource_calendar.erb +++ b/views/resource_calendar.erb @@ -1,6 +1,11 @@ <% reservation_groups = reservations.group_by do |reservation| reservation.start_time.in_time_zone.strftime("%Y/%m/%d") -end %> +end + +HALF_HOUR_HEIGHT = 28 # pixel height of half-hour sections in calendar. DO NOT CHANGE RESPONSIVELY +SIX_AM_MINUTES = 360 # start time of calendar +EIGHT_PM_MINUTES = 1200 # end time of calendar +%> <div id="pagetitle"> <div id="kioskLogo"></div> @@ -80,18 +85,22 @@ end %> closeds.each do |closed| start_time = closed[0] %> <% end_time = closed[1] %> - <% - if [((end_time - 360) / 30).floor, 35].min < 0 + <% + # if the end time is before 6 am, skip. + if ((end_time - SIX_AM_MINUTES) / 30).floor < 0 next end - top = ((start_time - 360) / 30) * 20 - height = (end_time - start_time) * 20 / 30 + # calculate top based off of 6 am start + top = ((start_time - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = (end_time - start_time) * HALF_HOUR_HEIGHT / 30 + # normalize top to 0, reduce height to accomodate if top < 0 height += top top = 0 - end - if top + height > 720 - height = 720 - top + end + # normalize the bottom to the bottom of the calendar at 8pm. + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top end %> <div class="status closed" title="Closed" style="top: <%= top %>px; height: <%= height %>px;"> @@ -104,17 +113,17 @@ end %> <% if record[:status] != 'open' && record[:status] != 'closed' %> <% start_time = record[:start] %> <% end_time = record[:end] %> - <% if [((end_time - 360) / 30).floor, 35].min < 0 + <% if ((end_time - SIX_AM_MINUTES) / 30).floor < 0 next end - top = ((start_time - 360) / 30) * 20 - height = (end_time - start_time) * 20 / 30 + top = ((start_time - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = (end_time - start_time) * HALF_HOUR_HEIGHT / 30 if top < 0 height += top top = 0 end - if top + height > 720 - height = 720 - top + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top end %> <div title="<%= record[:status].split('_').join(' ').capitalize_all %>" class="status <%= record[:status].downcase.split('_').join('-') %>" style="top: <%= top %>px; height: <%= height %>px;"> @@ -127,8 +136,8 @@ end %> <% day_events = reservation_groups[day.strftime("%Y/%m/%d")] %> <% unless day_events.nil? %> <% day_events.sort{|a, b| a.start_time <=> b.start_time}.each do |res| %> - <% start_slot = [(((res.start_time.in_time_zone - day.midnight) / 60 - 360) / 30).floor, 0].max - end_slot = [(((res.end_time.in_time_zone - day.midnight) / 60 - 360) / 30).floor, 35].min + <% start_slot = [(((res.start_time.in_time_zone - day.midnight) / 60 - SIX_AM_MINUTES) / 30).floor, 0].max + end_slot = [(((res.end_time.in_time_zone - day.midnight) / 60 - SIX_AM_MINUTES) / 30).floor, 35].min if end_slot < 0 next end @@ -137,8 +146,8 @@ end %> over = slots[k] if slots[k] > over end over = [over,3].min - top = (((res.start_time.in_time_zone - day.midnight) / 60 - 360) / 30) * 28 - height = res.length * 28 / 30 + top = (((res.start_time.in_time_zone - day.midnight) / 60 - SIX_AM_MINUTES) / 30) * HALF_HOUR_HEIGHT + height = res.length * HALF_HOUR_HEIGHT / 30 top_overflow = false bottom_overflow = false if top < 0 @@ -146,11 +155,11 @@ end %> top = 0 top_overflow = true end - if top + height > 720 - height = 720 - top + if top + height > (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT + height = (EIGHT_PM_MINUTES / 30) * HALF_HOUR_HEIGHT - top bottom_overflow = true - reservation_groups[(day + 1.day).strftime("%Y/%m/%d")] ||= [] - reservation_groups[(day + 1.day).strftime("%Y/%m/%d")] << res + events_groups[(day + 1.day).strftime("%Y/%m/%d")] ||= [] + events_groups[(day + 1.day).strftime("%Y/%m/%d")] << res end %> <div class="event-container" style="top: <%= top %>px;"> @@ -263,11 +272,6 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque tick(); setInterval(tick, 60000); - - - - - }); }); </script> \ No newline at end of file diff --git a/views/space_home.erb b/views/space_home.erb index 67c88d8730d18585785b3c49b7371493c46c7bd0..cf3933789f48b1e28513c1a7e725946842b51ea8 100644 --- a/views/space_home.erb +++ b/views/space_home.erb @@ -38,6 +38,11 @@ You have no upcoming reservations. You can view upcoming trainings to get certif <form method="POST" action="/<%= @space.url_name %>/resources/<%= reservation.resource.id %>/cancel/<%= reservation.id %>/" class="delete-form"> <button class="wdn-button" type="submit">Remove</button> </form> + <% unless reservation.recurring_reference_id.nil? %> + <form method="POST" action="/<%= @space.url_name %>/resources/<%= reservation.resource.id %>/cancel_all/<%= reservation.recurring_reference_id %>/" class="delete-form delete-recurring-reservation"> + <button class="wdn-button" type="submit">Remove All</button> + </form> + <% end %> </td> </tr> <% end %> @@ -114,4 +119,16 @@ You have not signed up for any upcoming events. Why not check out the calendar t <% end %> </tbody> </table> -<% end %> \ No newline at end of file +<% end %> + +<script type="text/javascript"> +require(['jquery'], function($) { + $(document).ready(function() { + $('.delete-recurring-reservation').submit(function (submit) { + if (!window.confirm('This will remove all recurrences of this reservation. Are you sure?')) { + submit.preventDefault(); + } + }); + }); +}); +</script> \ No newline at end of file