diff --git a/app/assets/javascripts/osem-schedule.js b/app/assets/javascripts/osem-schedule.js index 5f1e7728..155b9ef7 100644 --- a/app/assets/javascripts/osem-schedule.js +++ b/app/assets/javascripts/osem-schedule.js @@ -1,3 +1,5 @@ +// ADMIN SCHEDULE + var url; // Should be initialize in Schedule.initialize var schedule_id; // Should be initialize in Schedule.initialize @@ -114,6 +116,31 @@ $(document).ready( function() { }); }); + +// PUBLIC SCHEDULE + +function starClicked(e){ + // stops the click from propagating + if (!e) var e = window.event; + e.preventDefault(); + e.cancelBubble = true; + if (e.stopPropagation) e.stopPropagation(); + + var callback = function(data) { + $(e.target).toggleClass('fa-star fa-star-o'); + } + + var params = { favourite_user_id: $(e.target).data('user') }; + + $.ajax({ + url: $(e.target).data('url'), + type: 'PATCH', + data: params, + success: callback, + dataType : 'json' + }); +} + function eventClicked(e, element){ var url = $(element).data('url'); if(e.ctrlKey) diff --git a/app/assets/stylesheets/osem-schedule.scss b/app/assets/stylesheets/osem-schedule.scss index 1cf1efec..87849148 100644 --- a/app/assets/stylesheets/osem-schedule.scss +++ b/app/assets/stylesheets/osem-schedule.scss @@ -114,7 +114,7 @@ a.unstyled-link { .program-dropdown, .schedule-dropdown{ margin-left: 20%; margin-right: 20%; - margin-top: 40px; + margin-top: 10px; margin-bottom: 20px; } @@ -221,20 +221,6 @@ td.no-padding{ cursor: pointer; } -.program-dropdown{ - margin-left: 20%; - margin-right: 20%; - margin-top: 40px; -} - -.program-dropdown > button{ - width: 100%; -} - -.program-dropdown > .dropdown-menu{ - width: 100%; -} - .no-events-day{ color: #D8D8D8 !important; } @@ -254,6 +240,15 @@ td.no-padding{ opacity: 0.5; } +#star{ + margin-top: 8px; +} + +#star-events{ + margin-right: 5px; + vertical-align: text-top; +} + /* Small devices (tablets, 768px and up) */ @media (min-width: 768px) { .room, .event-title{ diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 54ca58c9..f1c15ad2 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -81,11 +81,22 @@ class ProposalsController < ApplicationController redirect_to conference_program_proposals_path(conference_id: @conference.short_title), notice: 'Proposal was successfully updated.' else - flash.now[:error] = "Could not update proposal: #{@event.errors.full_messages.join(', ')}" + flash[:error] = "Could not update proposal: #{@event.errors.full_messages.join(', ')}" render action: 'edit' end end + def toggle_favorite + user = User.find(params[:favourite_user_id]) + users = @event.favourite_users + if users.include? user + @event.favourite_users.delete(user) + else + @event.favourite_users << user + end + render json: {} + end + def withdraw authorize! :update, @event @url = conference_program_proposal_path(@conference.short_title, params[:id]) diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index fd03abc6..dfdcb8b9 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -3,6 +3,7 @@ class SchedulesController < ApplicationController load_and_authorize_resource before_action :respond_to_options + before_action :favourites load_resource :conference, find_by: :short_title load_resource :program, through: :conference, singleton: true, except: :index before_action :load_withdrawn_event_schedules, only: [:show, :events] @@ -58,9 +59,17 @@ class SchedulesController < ApplicationController @events_schedules = @program.selected_event_schedules( includes: [:room, { event: %i[track event_type speakers submitter] }] ) - @events_schedules = [] unless @events_schedules - @unscheduled_events = @program.events.confirmed - @events_schedules.map(&:event) + @events_schedules = @events_schedules.select{ |e| e.event.favourite_users.exists?(current_user.id) } if @events_schedules && current_user && @favourites + @events_schedules = [] unless @events_schedules + @favourites = params[:favourites] == 'true' + + @unscheduled_events = if @program.selected_schedule + @program.events.confirmed - @events_schedules.map(&:event) + else + @program.events.confirmed + end + @unscheduled_events = @unscheduled_events.select{ |e| e.favourite_users.exists?(current_user.id) } if current_user && @favourites day = @conference.current_conference_day @tag = day.strftime('%Y-%m-%d') if day @@ -72,6 +81,10 @@ class SchedulesController < ApplicationController private + def favourites + @favourites = params[:favourites] == 'true' + end + def respond_to_options respond_to do |format| format.html { head :ok } diff --git a/app/models/ability.rb b/app/models/ability.rb index 26ce7bf5..73ece195 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -109,6 +109,10 @@ class Ability event.users.include?(user) end + can :toggle_favorite, Event do |event| + event.scheduled? + end + # can manage the commercials of their own events can :manage, Commercial, commercialable_type: 'Event', commercialable_id: user.events.pluck(:id) diff --git a/app/models/event.rb b/app/models/event.rb index c8872429..414a2b3e 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -34,9 +34,12 @@ class Event < ApplicationRecord belongs_to :difficulty_level belongs_to :program + has_and_belongs_to_many :favourite_users, class_name: 'User' + accepts_nested_attributes_for :event_users, allow_destroy: true accepts_nested_attributes_for :speakers, allow_destroy: true accepts_nested_attributes_for :users + accepts_nested_attributes_for :favourite_users before_create :generate_guid diff --git a/app/models/user.rb b/app/models/user.rb index aca92cf9..1e83ed7b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -86,6 +86,8 @@ class User < ApplicationRecord has_many :booths, through: :booth_requests has_many :survey_replies has_many :survey_submissions + has_and_belongs_to_many :favourite_events, class_name: 'Event' + accepts_nested_attributes_for :roles scope :admin, -> { where(is_admin: true) } diff --git a/app/views/schedules/_carousel.html.haml b/app/views/schedules/_carousel.html.haml index 785a0681..fc94883c 100644 --- a/app/views/schedules/_carousel.html.haml +++ b/app/views/schedules/_carousel.html.haml @@ -29,9 +29,10 @@ %td.room{ style: "height: #{ td_height(@rooms) }px;" } .room.elipsis.break-words{ style: "-webkit-line-clamp: #{ room_lines(@rooms) }; height: #{ room_height(@rooms) }px;" } = room.name - - event_schedules = (@event_schedules_by_room_id[room.id] || []).select{ |e| (e.end_time > start_time) && (e.start_time <= (start_time + hrs_per_slide.hour)) } + - event_schedules = event_schedules.select{ |e| e.event.favourite_users.exists?(current_user.id) } if current_user && @favourites + - interval_count.times do |offset| - if span > 1 - span -= 1 diff --git a/app/views/schedules/_event.html.haml b/app/views/schedules/_event.html.haml index 25f4e1a0..92f0dd1e 100644 --- a/app/views/schedules/_event.html.haml +++ b/app/views/schedules/_event.html.haml @@ -4,11 +4,15 @@ = image_tag speaker.gravatar_url, :class => "img-circle pull-right all-speaker-pic", | :alt => speaker.name, | :title => speaker.name | - %p = canceled_replacement_event_label(event, event_schedule) = replacement_event_notice(event_schedule) - + - if current_user + = link_to('#', onClick: 'starClicked();') do + %span#star-events{ class: "fa fa-lg #{ event.favourite_users.exists?(current_user.id) ? 'fa-star' : 'fa-star-o' }", | + "aria-hidden" => "true", | + "data-url" => toggle_favorite_conference_program_proposal_path(@conference.short_title, event.id), | + "data-user" => current_user.id } %span.h3 = event.title %br diff --git a/app/views/schedules/_schedule_item.html.haml b/app/views/schedules/_schedule_item.html.haml index b6d8708e..d72f5e33 100644 --- a/app/views/schedules/_schedule_item.html.haml +++ b/app/views/schedules/_schedule_item.html.haml @@ -1,12 +1,18 @@ %td.event{ width: "#{ width * span }%" , | colspan: span, | role: "button" } - %a.unstyled-link{href: url_for(conference_program_proposal_path(@conference.short_title, event.id))} + %div{ onClick: 'eventClicked(event, this);', "data-url" => "#{url_for(conference_program_proposal_path(@conference.short_title, event.id))}" } %div{ class: "elipsis break-words event-title", | style: "-webkit-line-clamp: #{ event_lines(@rooms) }; height: #{ event_height(@rooms) }px;"} | = canceled_replacement_event_label(event, event_schedule, 'schedule-label') + - if current_user + = link_to('#', onClick: 'starClicked();') do + %span#star{ class: "fa fa-lg #{ event.favourite_users.exists?(current_user.id) ? 'fa-star' : 'fa-star-o' }", | + "aria-hidden" =>"true", | + "data-url" => toggle_favorite_conference_program_proposal_path(@conference.short_title, event.id), | + "data-user" => current_user.id } = event.title - event.speakers_ordered.each do |speaker| @@ -14,4 +20,3 @@ :alt => speaker.name, | :title => speaker.name, | :style => "height: #{ speaker_height(@rooms) }px; width: #{ speaker_width(@rooms) }px;" - diff --git a/app/views/schedules/_schedule_tabs.html.haml b/app/views/schedules/_schedule_tabs.html.haml index 0317f8bc..30dd5ab7 100644 --- a/app/views/schedules/_schedule_tabs.html.haml +++ b/app/views/schedules/_schedule_tabs.html.haml @@ -2,6 +2,6 @@ / Nav tabs %ul.nav.nav-tabs{ role: "tablist" } %li{ class: "schedule #{ 'active' if active == 'schedule' }", role: "presentation" } - = link_to('Schedule', conference_schedule_path(@conference.short_title)) + = link_to('Schedule', conference_schedule_path(@conference.short_title, favourites: @favourites)) %li{ class: "program #{ 'active' if active == 'program' }", role: "presentation" } - = link_to('All events', events_conference_schedule_path(@conference.short_title)) + = link_to('All events', events_conference_schedule_path(@conference.short_title, favourites: @favourites)) diff --git a/app/views/schedules/events.html.haml b/app/views/schedules/events.html.haml index b6e87e31..1ccd3b80 100644 --- a/app/views/schedules/events.html.haml +++ b/app/views/schedules/events.html.haml @@ -5,6 +5,13 @@ %h1.text-center Program for = @conference.title + - if current_user + .row + .col-md-12.text-center + = button_to (@favourites ? 'All events' : 'Only favourites events'), + events_conference_schedule_path(@conference.short_title), + params: { favourites: !@favourites }, + method: :get, class: 'btn btn-success' .dropdown.program-dropdown %button{ type: "button", class: "btn btn-default dropdown-toggle", 'data-toggle' => "dropdown" } Dates diff --git a/app/views/schedules/show.html.haml b/app/views/schedules/show.html.haml index 7716b63c..e3ea2cbb 100644 --- a/app/views/schedules/show.html.haml +++ b/app/views/schedules/show.html.haml @@ -5,6 +5,13 @@ %h1.text-center Schedule for = @conference.title + - if current_user + .row + .col-md-12.text-center + = button_to (@favourites ? 'All events' : 'Only favourites events'), + conference_schedule_path(@conference.short_title), + params: { favourites: !@favourites }, + method: :get, class: 'btn btn-success' .dropdown.schedule-dropdown %button{ type: "button", class: "btn btn-default dropdown-toggle", 'data-toggle' => "dropdown" } = @day diff --git a/config/routes.rb b/config/routes.rb index 730d43cf..a3b3d4d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -166,6 +166,7 @@ Osem::Application.routes.draw do patch '/withdraw' => 'proposals#withdraw' patch '/confirm' => 'proposals#confirm' patch '/restart' => 'proposals#restart' + patch :toggle_favorite end end resources :tracks, except: :destroy do diff --git a/db/migrate/20160811143427_create_events_users.rb b/db/migrate/20160811143427_create_events_users.rb new file mode 100644 index 00000000..64a7fa6e --- /dev/null +++ b/db/migrate/20160811143427_create_events_users.rb @@ -0,0 +1,8 @@ +class CreateEventsUsers < ActiveRecord::Migration + def change + create_table :events_users, id: false do |t| + t.references :event + t.references :user + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 298e77a3..22184144 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -263,6 +263,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_21_114727) do t.datetime "created_at" end + create_table "events_users", id: false, force: :cascade do |t| + t.integer "event_id" + t.integer "user_id" + end + create_table "lodgings", force: :cascade do |t| t.string "name" t.text "description"