Skip to content
Snippets Groups Projects
Commit f3b286ef authored by Tyler R Lemburg's avatar Tyler R Lemburg
Browse files

Download links for .ICS for reservations and events

Also includes various bugfixes
parent 3e1e6cb3
No related branches found
No related tags found
No related merge requests found
...@@ -11,6 +11,7 @@ gem 'unicorn' ...@@ -11,6 +11,7 @@ gem 'unicorn'
gem 'pony' gem 'pony'
gem 'rest-client' gem 'rest-client'
gem 'rack-cas' gem 'rack-cas'
gem 'icalendar'
group :development do group :development do
gem 'shotgun' gem 'shotgun'
......
...@@ -44,6 +44,7 @@ GEM ...@@ -44,6 +44,7 @@ GEM
http-cookie (1.0.2) http-cookie (1.0.2)
domain_name (~> 0.5) domain_name (~> 0.5)
i18n (0.7.0) i18n (0.7.0)
icalendar (2.4.1)
json (1.8.3) json (1.8.3)
kgio (2.10.0) kgio (2.10.0)
less (2.6.0) less (2.6.0)
...@@ -138,6 +139,7 @@ DEPENDENCIES ...@@ -138,6 +139,7 @@ DEPENDENCIES
bcrypt bcrypt
guard guard
guard-less guard-less
icalendar
mysql mysql
pony pony
rack-cas rack-cas
......
...@@ -22,7 +22,7 @@ Using local resources ...@@ -22,7 +22,7 @@ Using local resources
Quick Tutorial Quick Tutorial
============== ==============
1. Service spaces refer to different silos of the University that will utilize resources. E.g. the math department, the Honors program, or University Communications. 1. Service spaces refer to different silos of the University that will utilize resources. E.g. the math department, the Honors program, or University Communication.
2. Super Admins of a space can do anything, including giving others access and privileges to the space. 2. Super Admins of a space can do anything, including giving others access and privileges to the space.
3. Resources are created, and then may be reserved by anyone who has the User Access privilege in the space. 3. Resources are created, and then may be reserved by anyone who has the User Access privilege in the space.
4. Events *may* include a resource reservation but do not have to. 4. Events *may* include a resource reservation but do not have to.
......
...@@ -69,4 +69,21 @@ class Event < ActiveRecord::Base ...@@ -69,4 +69,21 @@ class Event < ActiveRecord::Base
self.imagedata = nil self.imagedata = nil
self.save self.save
end end
def to_ics
calendar = Icalendar::Calendar.new
ical = Icalendar::Event.new
ical.dtstart = self.start_time.in_time_zone.strftime("%Y%m%dT%H%M%S")
ical.dtend = self.end_time.in_time_zone.strftime("%Y%m%dT%H%M%S")
ical.summary = self.title
ical.description = self.description
ical.location = self.location.name
ical.uid = "unl_resource_scheduler_#{ENV['RACK_ENV']}_event_#{self.id}"
calendar.add_event(ical)
calendar.to_ical
end
def download_link
"/#{service_space.url_name}/events/#{id}/event.ics"
end
end end
\ No newline at end of file
require 'active_record' require 'active_record'
require 'models/resource' require 'models/resource'
require 'icalendar'
class Reservation < ActiveRecord::Base class Reservation < ActiveRecord::Base
belongs_to :resource belongs_to :resource
...@@ -25,4 +26,21 @@ class Reservation < ActiveRecord::Base ...@@ -25,4 +26,21 @@ class Reservation < ActiveRecord::Base
end end
((end_time - start_time) / 60).to_i ((end_time - start_time) / 60).to_i
end end
def to_ics
calendar = Icalendar::Calendar.new
ical = Icalendar::Event.new
ical.dtstart = self.start_time.in_time_zone.strftime("%Y%m%dT%H%M%S")
ical.dtend = self.end_time.in_time_zone.strftime("%Y%m%dT%H%M%S")
ical.summary = self.title
ical.description = self.description
ical.location = self.resource.name
ical.uid = "unl_resource_scheduler_#{ENV['RACK_ENV']}_reservation_#{self.id}"
calendar.add_event(ical)
calendar.to_ical
end
def download_link
"/#{resource.service_space.url_name}/resources/#{resource.id}/reservation/#{id}/reservation.ics"
end
end end
\ No newline at end of file
...@@ -6,6 +6,7 @@ require 'models/resource_field' ...@@ -6,6 +6,7 @@ require 'models/resource_field'
require 'models/resource_field_data' require 'models/resource_field_data'
class Resource < ActiveRecord::Base class Resource < ActiveRecord::Base
belongs_to :service_space
has_many :reservations, dependent: :destroy has_many :reservations, dependent: :destroy
has_many :resource_approvers, dependent: :destroy has_many :resource_approvers, dependent: :destroy
has_many :resource_authorizations, dependent: :destroy has_many :resource_authorizations, dependent: :destroy
......
This diff is collapsed.
...@@ -72,9 +72,9 @@ post '/:service_space_url_name/admin/events/create/?' do ...@@ -72,9 +72,9 @@ post '/:service_space_url_name/admin/events/create/?' do
end end
event = Event.new event = Event.new
event.service_space_id = @space.id
event.set_image_data(params) event.set_image_data(params)
event.set_data(params) event.set_data(params)
event.service_space_id = @space.id
if params.has_key?('reserve_resource') && params['reserve_resource'] == 'on' if params.has_key?('reserve_resource') && params['reserve_resource'] == 'on'
# we need to create a reservation for the resource on the appropriate time # we need to create a reservation for the resource on the appropriate time
......
...@@ -3,6 +3,7 @@ require 'models/space_hour' ...@@ -3,6 +3,7 @@ require 'models/space_hour'
get '/:service_space_url_name/calendar/?' do get '/:service_space_url_name/calendar/?' do
load_service_space load_service_space
check_login
@breadcrumbs << {:text => "#{@space.name} Calendar"} @breadcrumbs << {:text => "#{@space.name} Calendar"}
......
...@@ -55,6 +55,19 @@ post '/:service_space_url_name/events/:event_id/sign_up/?' do ...@@ -55,6 +55,19 @@ post '/:service_space_url_name/events/:event_id/sign_up/?' do
end end
end end
get '/:service_space_url_name/events/:event_id/event.ics' do
require_login
load_service_space
event = Event.find_by(:service_space_id => @space.id, :id => params[:event_id])
if event.nil?
raise Sinatra::NotFound
end
attachment "#{event.title}.ics"
event.to_ics
end
post '/:service_space_url_name/events/:event_id/remove_signup/?' do post '/:service_space_url_name/events/:event_id/remove_signup/?' do
require_login require_login
load_service_space load_service_space
...@@ -67,7 +80,7 @@ post '/:service_space_url_name/events/:event_id/remove_signup/?' do ...@@ -67,7 +80,7 @@ post '/:service_space_url_name/events/:event_id/remove_signup/?' do
if signup.nil? if signup.nil?
flash :alert, 'Not Found', 'That signup was not found.' flash :alert, 'Not Found', 'That signup was not found.'
redirect '/home/' redirect "#{params[:service_space_url_name]}"
end end
signup.delete signup.delete
......
...@@ -442,7 +442,6 @@ post '/:service_space_url_name/resources/:resource_id/reserve/?' do ...@@ -442,7 +442,6 @@ post '/:service_space_url_name/resources/:resource_id/reserve/?' do
redirect "/#{@space.url_name}/resources/#{resource.id}/calendar/#{params[:kiosk_mode] ? '?kiosk_mode=true' : ''}" redirect "/#{@space.url_name}/resources/#{resource.id}/calendar/#{params[:kiosk_mode] ? '?kiosk_mode=true' : ''}"
end end
get '/:service_space_url_name/resources/:resource_id/edit_reservation/:reservation_id/?' do get '/:service_space_url_name/resources/:resource_id/edit_reservation/:reservation_id/?' do
require_login require_login
load_service_space load_service_space
...@@ -658,4 +657,20 @@ post '/:service_space_url_name/resources/:resource_id/cancel_all/:recurring_refe ...@@ -658,4 +657,20 @@ post '/:service_space_url_name/resources/:resource_id/cancel_all/:recurring_refe
redirect back redirect back
end end
get '/:service_space_url_name/resources/:resource_id/reservation/:reservation_id/reservation.ics' do
require_login
load_service_space
resource = Resource.find_by(:service_space_id => @space.id, :id => params[:resource_id])
if resource.nil?
raise Sinatra::NotFound
end
reservation = Reservation.find_by(:resource_id => resource.id, :id => params[:reservation_id])
if reservation.nil?
raise Sinatra::NotFound
end
attachment "#{reservation.title}.ics"
reservation.to_ics
end
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
float: right; float: right;
} }
.wdn-button-small {
font-size: 80%;
}
.tooltip { .tooltip {
> div { > div {
position: absolute; position: absolute;
...@@ -127,7 +131,7 @@ ...@@ -127,7 +131,7 @@
display: none; display: none;
text-align: center; text-align: center;
font-size: 0.85em; font-size: 0.85em;
line-height: 1.6em; line-height: 1.3em;
position: absolute; position: absolute;
font-size: .8125rem; font-size: .8125rem;
ul li { ul li {
...@@ -222,13 +226,9 @@ label.day-header { ...@@ -222,13 +226,9 @@ label.day-header {
position: relative; position: relative;
z-index: 1; z-index: 1;
background-color: #d8d8d8; background-color: #d8d8d8;
<<<<<<< HEAD
padding: .5em 30px 0 5em;
=======
padding: .5em 46px 0 10%; padding: .5em 46px 0 10%;
@media only screen and (min-width : 900px) {padding: .5em 46px 0 7.5%;} @media only screen and (min-width : 900px) {padding: .5em 46px 0 7.5%;}
@media only screen and (min-width : 1200px) {padding: .5em 46px 0 5%;} @media only screen and (min-width : 1200px) {padding: .5em 46px 0 5%;}
>>>>>>> e7707fbcac10915ccb82fe9215a45a3d672b3168
} }
.calendar { .calendar {
......
...@@ -14,14 +14,6 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar ...@@ -14,14 +14,6 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar
</h3> </h3>
</div> </div>
<!-- <div style="margin-bottom: 16px;">
<h4 style="text-align: center; margin: 0;">
<%= month = sunday.strftime('%B %Y') %><%= (month2 = (sunday+6.days).strftime('%B %Y')) == month ? '' : " - #{month2}" %>
</h4>
<a href="/<%= @space.url_name %>/calendar/?date=<%= (date-7.days).strftime('%Y-%m-%d') %>" class="wdn-button wdn-button-triad" id="prev-week">&lt; PREV</a>
<a href="/<%= @space.url_name %>/calendar/?date=<%= (date+7.days).strftime('%Y-%m-%d') %>" class="wdn-button wdn-button-triad" style="float: right;" id="next-week">NEXT &gt;</a>
</div> -->
<div class="calendar"> <div class="calendar">
<div class="calendar-header"> <div class="calendar-header">
<a href="/<%= @space.url_name %>/calendar/?date=<%= (date-7.days).strftime('%Y-%m-%d') %><%= defined?(kiosk_mode) && kiosk_mode == 'true' ? '&kiosk_mode=true' : ''%>" id="prev-week" class="schedulericon-angle-circled-left"><span class="wdn-text-hidden">Previous Week</span></a> <a href="/<%= @space.url_name %>/calendar/?date=<%= (date-7.days).strftime('%Y-%m-%d') %><%= defined?(kiosk_mode) && kiosk_mode == 'true' ? '&kiosk_mode=true' : ''%>" id="prev-week" class="schedulericon-angle-circled-left"><span class="wdn-text-hidden">Previous Week</span></a>
...@@ -41,7 +33,7 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar ...@@ -41,7 +33,7 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar
<div class="time-chart"> <div class="time-chart">
<% (12..39).each do |j| %> <% (12..39).each do |j| %>
<div class="calendar-half-hour"> <div class="calendar-half-hour">
<label><%= "#{(j / 2) % 12 + (j==24?12:0)} #{j>=24?'PM':'AM'}" if j % 2 == 0 %></label> <label><%= "#{(j / 2) % 12 + (j==24 ? 12 : 0)} #{j>=24 ? 'PM': 'AM'}" if j % 2 == 0 %></label>
</div> </div>
<% end %> <% end %>
</div> </div>
...@@ -165,8 +157,8 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar ...@@ -165,8 +157,8 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar
<div class="tooltip <%= top >= 50 ? "hang-above" : "hang-below" %> <%= day.strftime("%^a") == "FRI" ? "hang-left" : "hang-right" %>"> <div class="tooltip <%= top >= 50 ? "hang-above" : "hang-below" %> <%= day.strftime("%^a") == "FRI" ? "hang-left" : "hang-right" %>">
<div> <div>
<h6 style="margin-top: 0px; margin-bottom: .25em;"><%= res.title ? (res.title.empty? ? 'Reserved' : res.title) : 'Reserved' %></h6> <h6 style="margin-top: 0px; margin-bottom: .25em;"><%= res.title ? (res.title.empty? ? 'Reserved' : res.title) : 'Reserved' %></h6>
<p class="eventicon-clock"><%= res.start_time.in_time_zone.strftime('%I:%M %p') %> - <%= res.end_time.in_time_zone.strftime('%I:%M %p') %></p> <p style="margin-bottom: 10px;" class="eventicon-clock"><%= res.start_time.in_time_zone.strftime('%I:%M %p') %> - <%= res.end_time.in_time_zone.strftime('%I:%M %p') %></p>
<p>Reserved by: <span class="italic"><%= res.user.full_name rescue nil %></span></p> <p style="margin-bottom: 10px;"><a href="<%= res.info_link %>"" class="wdn-button wdn-button-brand wdn-button-small">View</a> <a href="<%= res.download_link %>" class="download-ics wdn-button wdn-button-triad wdn-button-small">Download</a></p>
<div class="close"><a href="#">&times;</a></div> <div class="close"><a href="#">&times;</a></div>
</div> </div>
</div> </div>
...@@ -207,11 +199,6 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque ...@@ -207,11 +199,6 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque
if($tooltip.hasClass("hang-below")){ if($tooltip.hasClass("hang-below")){
$tooltip.css("margin-bottom", ($tooltip.find("div").height()+35)*-1); $tooltip.css("margin-bottom", ($tooltip.find("div").height()+35)*-1);
} }
$(document).one("click",function(){
$tooltip.show().hide();
$eventContainer.css('z-index', closedZ);
})
}); });
$('.tooltip div.close a').click(function (click) { $('.tooltip div.close a').click(function (click) {
...@@ -228,7 +215,7 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque ...@@ -228,7 +215,7 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque
}, },
setTop: "95px" setTop: "95px"
} }
); );
}); });
function tick(){ function tick(){
......
...@@ -38,14 +38,14 @@ ...@@ -38,14 +38,14 @@
<% elsif @user %> <% elsif @user %>
<% # the user is logged in but not signed up %> <% # the user is logged in but not signed up %>
<% if event.max_signups.nil? || event.signups.count < event.max_signups %> <% if event.max_signups.nil? || event.signups.count < event.max_signups %>
<form action="/<%= @space.url_name %>/events/<%= event.id %>/sign_up/" method="POST"> <form action="/<%= @space.url_name %>/events/<%= event.id %>/sign_up/" class="delete-form" method="POST">
<button type="submit" class="wdn-button wdn-button-brand"> <button type="submit" class="wdn-button wdn-button-brand">
<% if event.type.description == 'Free Event' %> <% if event.type.description == 'Free Event' %>
Note event on my homepage Note event on my homepage
<% else %> <% else %>
Sign up for this event Sign up for this event
<% end %> <% end %>
</a> </button>
</form> </form>
<% else %> <% else %>
All slots for this event are filled. All slots for this event are filled.
...@@ -54,9 +54,10 @@ ...@@ -54,9 +54,10 @@
<% # a non user. May still sign up for the event UNLESS it is a tool training %> <% # a non user. May still sign up for the event UNLESS it is a tool training %>
<% if event.type.description != 'Machine Training' %> <% if event.type.description != 'Machine Training' %>
<% if event.max_signups.nil? || event.signups.count < event.max_signups %> <% if event.max_signups.nil? || event.signups.count < event.max_signups %>
<a class="wdn-button wdn-button-brand" href="/<%= @space.url_name %>/events/<%= event.id %>/sign_up_as_non_member/">Sign up for this event</a> <a class="wdn-button wdn-button-brand" href="/<%= @space.url_name %>/events/<%= event.id %>/sign_up_as_non_member/">Sign up for this event</a>
<% else %> <% else %>
All slots for this event are filled. All slots for this event are filled.
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
\ No newline at end of file <a href="<%= event.download_link %>" class="download-ics wdn-button wdn-button-triad">Download</a>
\ No newline at end of file
...@@ -47,7 +47,7 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar ...@@ -47,7 +47,7 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar
<div class="time-chart"> <div class="time-chart">
<% (12..39).each do |j| %> <% (12..39).each do |j| %>
<div class="calendar-half-hour"> <div class="calendar-half-hour">
<label><%= "#{(j / 2) % 12 + (j==24?12:0)} #{j>=24? 'PM':'AM'}" if j % 2 == 0 %></label> <label><%= "#{(j / 2) % 12 + (j==24?12:0)} #{j >= 24 ? 'PM' : 'AM'}" if j % 2 == 0 %></label>
</div> </div>
<% end %> <% end %>
</div> </div>
...@@ -171,8 +171,9 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar ...@@ -171,8 +171,9 @@ EIGHT_PM_MINUTES = 1200 # end time of calendar
<div class="tooltip <%= top >= 50 ? "hang-above" : "hang-below" %> <%= day.strftime("%^a") == "FRI" ? "hang-left" : "hang-right" %>"> <div class="tooltip <%= top >= 50 ? "hang-above" : "hang-below" %> <%= day.strftime("%^a") == "FRI" ? "hang-left" : "hang-right" %>">
<div> <div>
<h6 style="margin-top: 0px; margin-bottom: .25em;"><%= res.title ? (res.title.empty? ? 'Reserved' : res.title) : 'Reserved' %></h6> <h6 style="margin-top: 0px; margin-bottom: .25em;"><%= res.title ? (res.title.empty? ? 'Reserved' : res.title) : 'Reserved' %></h6>
<p class="eventicon-clock"><%= res.start_time.in_time_zone.strftime('%I:%M %p') %> - <%= res.end_time.in_time_zone.strftime('%I:%M %p') %></p> <p style="margin-bottom: 10px;" class="eventicon-clock"><%= res.start_time.in_time_zone.strftime('%I:%M %p') %> - <%= res.end_time.in_time_zone.strftime('%I:%M %p') %><br>
<p>Reserved by: <span class="italic"><%= res.user.full_name rescue nil %></span></p> Reserved by: <span class="italic"><%= res.user.full_name rescue nil %></span></p>
<p style="margin-bottom: 10px;"><a href="<%= res.download_link %>" class="download-ics wdn-button wdn-button-triad wdn-button-small">Download</a></p>
<div class="close"><a href="#">&times;</a></div> <div class="close"><a href="#">&times;</a></div>
</div> </div>
</div> </div>
...@@ -210,14 +211,10 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque ...@@ -210,14 +211,10 @@ require(['jquery', '/js/functions.js', '/js/jquery.mousewheel.min.js', '/js/jque
var $tooltip = $(this).next(".tooltip"); var $tooltip = $(this).next(".tooltip");
$tooltip.show(); $tooltip.show();
if($tooltip.hasClass("hang-below")){ if ($tooltip.hasClass("hang-below")){
$tooltip.css("margin-bottom", ($tooltip.find("div").height()+35)*-1); $tooltip.css("margin-bottom", ($tooltip.find("div").height()+35)*-1);
} }
$(document).one("click",function(){
$tooltip.show().hide();
$eventContainer.css('z-index', closedZ);
})
}); });
$('.tooltip div.close a').click(function (click) { $('.tooltip div.close a').click(function (click) {
......
...@@ -34,6 +34,7 @@ You have no upcoming reservations. You can view upcoming trainings to get certif ...@@ -34,6 +34,7 @@ You have no upcoming reservations. You can view upcoming trainings to get certif
<%= reservation.length %> minutes <%= reservation.length %> minutes
</td> </td>
<td class="table-actions"> <td class="table-actions">
<a href="<%= reservation.download_link %>" class="wdn-button wdn-button-triad">Download</a>
<a href="/<%= @space.url_name %>/resources/<%= reservation.resource.id %>/edit_reservation/<%= reservation.id %>/" class="wdn-button wdn-button-brand">Edit</a> <a href="/<%= @space.url_name %>/resources/<%= reservation.resource.id %>/edit_reservation/<%= reservation.id %>/" class="wdn-button wdn-button-brand">Edit</a>
<form method="POST" action="/<%= @space.url_name %>/resources/<%= reservation.resource.id %>/cancel/<%= reservation.id %>/" class="delete-form"> <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> <button class="wdn-button" type="submit">Remove</button>
...@@ -109,7 +110,8 @@ You have not signed up for any upcoming events. Why not check out the calendar t ...@@ -109,7 +110,8 @@ You have not signed up for any upcoming events. Why not check out the calendar t
<%= event.location.name %> <%= event.location.name %>
</td> </td>
<td class="table-actions"> <td class="table-actions">
<form action="/events/<%= event.id %>/remove_signup/" method="POST" class="delete-form"> <a href="<%= event.download_link %>" class="wdn-button wdn-button-triad">Download</a>
<form action="/<%= @space.url_name %>/events/<%= event.id %>/remove_signup/" method="POST" class="delete-form">
<button class="wdn-button" type="submit"> <button class="wdn-button" type="submit">
Remove Remove
</button> </button>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="bp960-wdn-col-two-thirds"> <div class="bp960-wdn-col-two-thirds">
<div class="wdn-footer-module"> <div class="wdn-footer-module">
<span role="heading" class="wdn-footer-heading">About UNL Resource Scheduler</span> <span role="heading" class="wdn-footer-heading">About UNL Resource Scheduler</span>
This application is developed and maintained by <a href="http://iim.unl.edu">Internet and Interactive Media (IIM)</a>, which is a partnership with University Communications and Information Technology Services. This application is developed and maintained by <a href="http://iim.unl.edu">Internet and Interactive Media (IIM)</a>, which is a partnership with University Communication and Information Technology Services.
</div> </div>
</div> </div>
<div class="bp960-wdn-col-one-third"> <div class="bp960-wdn-col-one-third">
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<ul class="wdn-related-links-v1"> <ul class="wdn-related-links-v1">
<li><a href="http://wdn.unl.edu/">Web Developer Network</a></li> <li><a href="http://wdn.unl.edu/">Web Developer Network</a></li>
<li><a href="http://iim.unl.edu/">Internet and Interactive Media</a></li> <li><a href="http://iim.unl.edu/">Internet and Interactive Media</a></li>
<li><a href="http://ucomm.unl.edu/">University Communications</a></li> <li><a href="http://ucomm.unl.edu/">University Communication</a></li>
<li><a href="http://its.unl.edu/">Information Technology Services</a></li> <li><a href="http://its.unl.edu/">Information Technology Services</a></li>
</ul> </ul>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment