diff --git a/app/assets/javascripts/event_suggestions.js b/app/assets/javascripts/event_suggestions.js new file mode 100644 index 00000000..efb8fdaf --- /dev/null +++ b/app/assets/javascripts/event_suggestions.js @@ -0,0 +1,152 @@ +// // Place all the behaviors and hooks related to the matching controller here. +// // All this logic will automatically be available in application.js. +EVENT_URL = '/events/' +SUGGEST_URL = '/new_event_suggestion' + +var ready; +ready = function() { + var typingTimer; + var doneTypingInterval = 1000; + $('#sugguest-form input').change(function() { + clearTimeout(typingTimer); + typingTimer = setTimeout(checkVacancy, doneTypingInterval); + }); + + $('#sugguest-form #selectpicker').change(function() { + clearTimeout(typingTimer); + typingTimer = setTimeout(checkVacancy, doneTypingInterval); + }); +// $(document).ajaxComplete(function(event, request) { +// var flash = $.parseJSON(request.getResponseHeader('X-Flash-Messages')); +// if(!flash) return; +// if(flash.notice) { $('.notice').html('
' + flash.notice + '
'); } +// if(flash.warning) { $('.notice').html('
' + flash.warning + '
');} +// if(flash.error) { /* code to display the 'error' flash */ alert("aa"); } +// }); + + function checkVacancy(e) { + rooms = [] + $("#selectpicker option:selected").each(function(){ rooms.push($(this).val());}); + + var data = { + event: { + starts_at_date: $('#event_suggestion_starts_at_date').val(), + starts_at_time: $('#event_suggestion_starts_at_time').val(), + ends_at_date: $('#event_suggestion_ends_at_date').val(), + ends_at_time: $('#event_suggestion_ends_at_time').val(), + room_ids: rooms + } + } + $.ajax({ + url: '/checkVacancy', + type: 'PATCH', + data: data, + dataType: 'json', + beforeSend: function(xhr) { + xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));}, + success:(function(data){ + if (!data["status"]) { + flashWarning(data); + } + else + clearFlash(); + // var data = JSON.parse(data); + // if(data['status']){ + // alert('true'); + // } + // else + // alert('false'); + }) + // }) + }); + }; + + function flashWarning(data) { + messages = [] + for(var i in data) { + if(isNum(i)) { + if(data[i]["rooms"].length > 1) { + room = "" + for(var j in rooms) { + if( room == "") { + room = data[i]["rooms"][j]; + } + else { + room += ", " + data[i]["rooms"][j]; + } + } + room_msg = "in den Räumen " + room; + } + else { + room_msg = "im Raum " + data[i].rooms[0] ; + } + var starts_at = convertUTCDateToLocalDate(new Date(data[i]["starts_at"])); + var ends_at = convertUTCDateToLocalDate(new Date(data[i]["ends_at"])); + var starts_at_date = starts_at.getDate() + "." + (starts_at.getMonth() + 1) + "." + starts_at.getFullYear(); + + if(isSameDay(starts_at, ends_at)) { + time_msg = "am " + starts_at_date + } + else { + var ends_at_date = ends_at.getDate() + "." + (ends_at.getMonth() + 1) + "." + ends_at.getFullYear(); + time_msg = "vom " + starts_at_date + " bis zum " + ends_at_date; + } + var starts_at_time = getTime(starts_at); + var ends_at_time = getTime(ends_at); + + time_msg += " von " + starts_at_time + " bis " + ends_at_time; + msg = "Ihre Alternative konfligiert mit dem Event " + i + " stattfindend " + " " + time_msg + " " + room_msg; + msg += suggestionLink(i); + messages.push(msg) + } + } + output = "" + for( var i in messages) { + output += '
' + messages[i] + '
'; + } + + $(".notice").html(output); + } + + function convertUTCDateToLocalDate(date) { + var newDate = new Date(date.getTime()+date.getTimezoneOffset()*60*1000); + + var offset = date.getTimezoneOffset() / 60; + var hours = date.getHours(); + + newDate.setHours(hours - offset); + + return newDate; + } + function isNum(val) { + return /^\d+$/.test(val); + } + + function isSameDay(startDate, endDate) { + if(startDate.getDate() == endDate.getDate() && startDate.getMonth() == endDate.getMonth()) { + return true; + } + else { + return false; + } + } + + function getTime(date) { + var hours = date.getHours(); + var mins = date.getMinutes(); + + var hourOutput = ((hours < 10) ? "0" + hours : hours); + var minOutput = ((mins < 10) ? "0" + mins : mins);(hours < 10 ); + return hourOutput + ":" + minOutput + " Uhr" + } + + function suggestionLink(id) { + return "
Alternative für Event " + id + " vorschlagen"; + } + + function clearFlash() { + $(".notice").html(""); + } +}; +$(document).ready(ready); +$(document).on('page:load', ready); \ No newline at end of file diff --git a/app/assets/javascripts/events.js b/app/assets/javascripts/events.js index f711d478..910d866e 100644 --- a/app/assets/javascripts/events.js +++ b/app/assets/javascripts/events.js @@ -2,4 +2,147 @@ // // All this logic will automatically be available in application.js. +var ready; +ready = function() { + var typingTimer; + var doneTypingInterval = 1000; + $('#event-form input').change(function() { + clearTimeout(typingTimer); + typingTimer = setTimeout(checkVacancy, doneTypingInterval); + }); + $('#event-form #selectpicker').change(function() { + clearTimeout(typingTimer); + typingTimer = setTimeout(checkVacancy, doneTypingInterval); + }); +// $(document).ajaxComplete(function(event, request) { +// var flash = $.parseJSON(request.getResponseHeader('X-Flash-Messages')); +// if(!flash) return; +// if(flash.notice) { $('.notice').html('
' + flash.notice + '
'); } +// if(flash.warning) { $('.notice').html('
' + flash.warning + '
');} +// if(flash.error) { /* code to display the 'error' flash */ alert("aa"); } +// }); + + function checkVacancy(e) { + rooms = [] + $("#selectpicker option:selected").each(function(){ rooms.push($(this).val());}); + + var data = { + event: { + starts_at_date: $('#event_starts_at_date').val(), + starts_at_time: $('#event_starts_at_time').val(), + ends_at_date: $('#event_ends_at_date').val(), + ends_at_time: $('#event_ends_at_time').val(), + room_ids: rooms + } + } + $.ajax({ + url: '/checkVacancy', + type: 'PATCH', + data: data, + dataType: 'json', + beforeSend: function(xhr) { + xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));}, + success:(function(data){ + if (!data["status"]) { + flashWarning(data); + } + else + clearFlash(); + // var data = JSON.parse(data); + // if(data['status']){ + // alert('true'); + // } + // else + // alert('false'); + }) + // }) + }); + }; + + function flashWarning(data) { + messages = [] + for(var i in data) { + if(isNum(i)) { + if(data[i]["rooms"].length > 1) { + room = "" + for(var j in rooms) { + if( room == "") { + room = data[i]["rooms"][j]; + } + else { + room += ", " + data[i]["rooms"][j]; + } + } + room_msg = "in den Räumen " + room; + } + else { + room_msg = "im Raum " + data[i].rooms[0] ; + } + var starts_at = convertUTCDateToLocalDate(new Date(data[i]["starts_at"])); + var ends_at = convertUTCDateToLocalDate(new Date(data[i]["ends_at"])); + var starts_at_date = starts_at.getDate() + "." + (starts_at.getMonth() + 1) + "." + starts_at.getFullYear(); + + if(isSameDay(starts_at, ends_at)) { + time_msg = "am " + starts_at_date + } + else { + var ends_at_date = ends_at.getDate() + "." + (ends_at.getMonth() + 1) + "." + ends_at.getFullYear(); + time_msg = "vom " + starts_at_date + " bis zum " + ends_at_date; + } + var starts_at_time = getTime(starts_at); + var ends_at_time = getTime(ends_at); + + time_msg += " von " + starts_at_time + " bis " + ends_at_time; + msg = "Ihr Event konfligiert mit dem Event ”" + data[i]["event_name"] + "” stattfindend " + " " + time_msg + " " + room_msg; + messages.push(msg) + } + } + output = "" + for( var i in messages) { + output += '
' + messages[i] + '
'; + } + + $(".notice").html(output); + } + + function convertUTCDateToLocalDate(date) { + var newDate = new Date(date.getTime()+date.getTimezoneOffset()*60*1000); + + var offset = date.getTimezoneOffset() / 60; + var hours = date.getHours(); + + newDate.setHours(hours - offset); + + return newDate; + } + function isNum(val) { + return /^\d+$/.test(val); + } + + function isSameDay(startDate, endDate) { + if(startDate.getDate() == endDate.getDate() && startDate.getMonth() == endDate.getMonth()) { + return true; + } + else { + return false; + } + } + + function getTime(date) { + var hours = date.getHours(); + var mins = date.getMinutes(); + + var hourOutput = ((hours < 10) ? "0" + hours : hours); + var minOutput = ((mins < 10) ? "0" + mins : mins);(hours < 10 ); + return hourOutput + ":" + minOutput + " Uhr" + } + + + + function clearFlash() { + $(".notice").html(""); + } +}; +$(document).ready(ready); +$(document).on('page:load', ready); \ No newline at end of file diff --git a/app/assets/javascripts/events_approval.js b/app/assets/javascripts/events_approval.js index dee720fa..bbfd26d3 100644 --- a/app/assets/javascripts/events_approval.js +++ b/app/assets/javascripts/events_approval.js @@ -1,2 +1,80 @@ // Place all the behaviors and hooks related to the matching controller here. // All this logic will automatically be available in application.js. +EVENT_URL = '/events/' +SUGGEST_URL = '/new_event_suggestion' +DECLINE_URL = '/decline' + + + +var ready; + +ready = function () { + $(".decline-btn").click(function(e) { + e.preventDefault(); + var id = this.id; + var event_id = id.split("#")[1] + $.ajax({ + url: EVENT_URL + event_id + ".json", + type: 'GET', + dataType: 'json', + beforeSend: function(xhr) { + xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));}, + success:(function(data){ + // alert("success"); + insertDeclineLink(data["id"]); + insertSuggestLink(data["id"]); + insertEventIntoModal(data); + $('#myModal').modal('toggle'); + }) + }); + }); + function insertEventIntoModal(data) { + var starts_at = convertUTCDateToLocalDate(new Date(data["starts_at"])); + var ends_at = convertUTCDateToLocalDate(new Date(data["ends_at"])); + $("#myModalLabel").html("Event " + data["id"]); + $("#event_id").html(data["id"]); + $("#event_name").html(data["name"]); + $("#event_description").html(data["description"]); + $("#event_rooms").html(getRoomNames(data["rooms"])); + $("#event_participant_count").html(data["participant_count"]); + $("#event_starts_at").html(starts_at.toLocaleString()); + $("#event_ends_at").html(ends_at.toLocaleString()); + $("#event_user").html(data["user"]); + + } + + function insertSuggestLink(id) { + $(".suggest-btn").attr("href", EVENT_URL + id + SUGGEST_URL); + } + + function insertDeclineLink(id) { + $(".modal-decline-btn").attr("href", EVENT_URL + id + DECLINE_URL); + } + + function getRoomNames(rooms) { + room_names = "" + for ( var i in rooms) { + if( room_names == "") { + room_names += rooms[i]["name"] + } + else { + room_names += ", " + rooms[i]["name"] + } + } + return room_names //rooms.toString(); + } + + function convertUTCDateToLocalDate(date) { + var newDate = new Date(date.getTime()+date.getTimezoneOffset()*60*1000); + + var offset = date.getTimezoneOffset() / 60; + var hours = date.getHours(); + + newDate.setHours(hours - offset); + + return newDate; + } +}; + +$(document).ready(ready); +$(document).on('page:load', ready); \ No newline at end of file diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css b/app/assets/stylesheets/bootstrap_and_overrides.css index f1683ae3..3513f79a 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css +++ b/app/assets/stylesheets/bootstrap_and_overrides.css @@ -12,4 +12,8 @@ font-family: 'Glyphicons Halflings'; src: url('/assets/glyphicons-halflings-regular.eot'); src: url('/assets/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('/assets/glyphicons-halflings-regular.woff') format('woff'), url('/assets/glyphicons-halflings-regular.ttf') format('truetype'), url('/assets/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} + +.modal-body { + height: 200px; } \ No newline at end of file diff --git a/app/controllers/event_suggestions_controller.rb b/app/controllers/event_suggestions_controller.rb new file mode 100644 index 00000000..f06bac93 --- /dev/null +++ b/app/controllers/event_suggestions_controller.rb @@ -0,0 +1,60 @@ +class EventSuggestionsController < ApplicationController + before_action :set_event_suggestion, only: [:show, :edit, :update, :destroy] + + def index + @event_suggestions = EventSuggestion.all + respond_to do |format| + format.html + format.js + end + end + + def show + # respond_with(@event_suggestion) + end + + def new + @event_suggestion = EventSuggestion.new + time = Time.new.getlocal + time -= time.sec + time += time.min % 15 + @event_suggestion.starts_at = time + @event_suggestion.ends_at = (time+(60*60)) + end + + def edit + end + + def create + logger.info event_suggestion_params.inspect + @event_suggestion = EventSuggestion.new(event_suggestion_params) + respond_to do |format| + if @event_suggestion.save + format.html { redirect_to @event_suggestion, notice: t('notices.successful_create', :model => EventSuggestion.model_name.human) } + format.json { render :show, status: :created, location: @event_suggestion } + else + format.html { render :new } + format.json { render json: @event_suggestion.errors, status: :unprocessable_entity } + end + end + end + + def update + @event_suggestion.update(event_suggestion_params) + respond_with(@event_suggestion) + end + + def destroy + @event_suggestion.destroy + respond_with(@event_suggestion) + end + + private + def set_event_suggestion + @event_suggestion = EventSuggestion.find(params[:id]) + end + + def event_suggestion_params + params.require(:event_suggestion).permit(:event_suggestion_id, :starts_at_date, :starts_at_time, :ends_at_date, :ends_at_time, :room_ids => []) + end +end diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index 5fb211d2..ab7ee726 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -1,14 +1,26 @@ class EventsController < ApplicationController - + skip_filter :verify_authenticity_token, :check_vacancy + # skip_filter :authenticate_user, :check_vacancy + skip_before_filter :authenticate_user! before_action :authenticate_user! - before_action :set_event, only: [:show, :edit, :update, :destroy, :approve, :new_event_template] + before_action :set_event, only: [:show, :edit, :update, :destroy, :approve, :decline, :new_event_template, :new_event_suggestion] load_and_authorize_resource - skip_load_and_authorize_resource :only =>[:index, :show, :new, :create, :new_event_template, :reset_filterrific] - + skip_load_and_authorize_resource :only =>[:index, :show, :new, :create, :new_event_template, :reset_filterrific, :check_vacancy, :new_event_suggestion, :decline, :approve] + after_filter :flash_to_headers, :only => :check_vacancy + def current_user_id current_user.id end + def flash_to_headers + if request.xhr? + #avoiding XSS injections via flash + flash_json = Hash[flash.map{|k,v| [k,ERB::Util.h(v)] }].to_json + response.headers['X-Flash-Messages'] = flash_json + flash.discard + end + end + # GET /events/1/new_event_template def new_event_template @event_template = EventTemplate.new @@ -19,6 +31,14 @@ def new_event_template render "event_templates/new" end + def new_event_suggestion + @event_suggestion = EventSuggestion.new + @event_suggestion.starts_at = @event.starts_at + @event_suggestion.ends_at = @event.ends_at + @event_suggestion.rooms = @event.rooms + render "event_suggestions/new" + end + # GET /events # GET /events.json def index @@ -55,21 +75,45 @@ def reset_filterrific end def approve - puts "approve" @event.update(approved: true) redirect_to events_approval_path(date: params[:date]) #params are not checked as date is no attribute of event and passed on as a html parameter end def decline - puts "decline" @event.update(approved: false) redirect_to events_approval_path(date: params[:date]) #params are not checked as date is no attribute of event and passed on as a html parameter end + def check_vacancy + checked_params = event_params + + @event = Event.new(event_params) + @event.user_id = current_user_id + + conflicting_events = @event.checkVacancy event_params[:room_ids] + + respond_to do |format| + if conflicting_events.empty? + flash[:notice] = "Vacant" + format.json { render :json => {status: true}} + else + flash[:warning] = "Not available" + + msg = Hash[conflicting_events.map { |event| + eventname = @event.id + eventname = event.name if (event.user_id == current_user_id || !event.is_private) + [event.id, {"event_name" => eventname, "starts_at" => event.starts_at, "ends_at" => event.ends_at, "rooms" => event.rooms.pluck(:name)}]}] + msg[:status]= false + format.json { render :json => msg} + end + end + end + # GET /events/1 # GET /events/1.json def show @user = User.find(@event.user_id).identity_url + logger.info @event.rooms.inspect end # GET /events/new @@ -90,33 +134,19 @@ def edit def sugguest end - def create_suggestion - @event = Event.new(event_params) - @event.user_id = current_user_id - logger.info @event.inspect - respond_to do |format| - if @event.save - if Event.checkVacancy(@event.starts_at_date, @event.ends_at_time, params[:event][:room_ids]) - format.html { redirect_to @event, notice: t('notices.successful_sugguest', :model => Event.model_name.human) } - format.json { render :show, status: :created, location: @event } - else - format.html { redirect_to @event, alert: t('alert.successful_sugguest_conflict', :model => Event.model_name.human) } - format.json { render json: @event.errors, status: :unprocessable_entity } - end - else - format.html { render :sugguest } - format.json { render json: @event.errors, status: :unprocessable_entity } - end - end - end # POST /events # POST /events.json def create @event = Event.new(event_params) @event.user_id = current_user_id logger.info @event.inspect + respond_to do |format| if @event.save + conflicting_events = @event.checkVacancy event_params[:room_ids] + if conflicting_events.size > 1 ## this event is also in the returned list + format.html { redirect_to @event, alert: t('alert.conflict_detected', :model => Event.model_name.human) } + end format.html { redirect_to @event, notice: t('notices.successful_create', :model => Event.model_name.human) } format.json { render :show, status: :created, location: @event } else @@ -131,6 +161,12 @@ def create def update respond_to do |format| if @event.update(event_params) + conflicting_events = @event.checkVacancy event_params[:room_ids] + logger.info "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + logger.info conflicting_events.inspect + if conflicting_events.size > 1 ## this event is also in the returned list + format.html { redirect_to @event, alert: t('alert.conflict_detected', :model => Event.model_name.human) } + end format.html { redirect_to @event, notice: t('notices.successful_update', :model => Event.model_name.human) } # format.json { render :show, status: :ok, location: @event } else @@ -158,6 +194,6 @@ def set_event # Never trust parameters from the scary internet, only allow the white list through. def event_params - params.require(:event).permit(:name, :description, :participant_count, :starts_at_date, :starts_at_time, :ends_at_date, :ends_at_time, :is_private, :show_only_my_events, :room_ids => []) + params.require(:event).permit(:event_id, :name, :description, :participant_count, :starts_at_date, :starts_at_time, :ends_at_date, :ends_at_time, :is_private, :show_only_my_events, :room_ids => []) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f4d1cb3f..ee455e72 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -9,7 +9,7 @@ def bootstrap_flash(options = {}) logger.info type type = type.to_sym type = :success if type == :notice - type = :warning if type == :alert #alert should be treated as a warning and not as a success + type = :warning if type == :alert #alert should be treated as a warning and not as a success type = :danger if type == :error next unless ALERT_TYPES.include?(type) diff --git a/app/helpers/event_suggestions_helper.rb b/app/helpers/event_suggestions_helper.rb new file mode 100644 index 00000000..4d20bbe6 --- /dev/null +++ b/app/helpers/event_suggestions_helper.rb @@ -0,0 +1,2 @@ +module EventSuggestionsHelper +end diff --git a/app/models/event.rb b/app/models/event.rb index f294145d..b10a6a65 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -31,13 +31,13 @@ class Event < ActiveRecord::Base validate :dates_cannot_be_in_the_past,:start_before_end_date - def dates_cannot_be_in_the_past - errors.add(:starts_at, "can't be in the past") if starts_at && starts_at < Date.today - errors.add(:ends_at, "can't be in the past") if ends_at && ends_at < Date.today - end - def start_before_end_date - errors.add(:starts_at, "start has to be before the end") if starts_at && starts_at && ends_at < starts_at - end + def dates_cannot_be_in_the_past + errors.add(I18n.t('time.starts_at'), I18n.t('errors.messages.date_in_the_past')) if starts_at < Date.today + errors.add(I18n.t('time.ends_at'), I18n.t('errors.messages.date_in_the_past')) if ends_at < Date.today + end + def start_before_end_date + errors.add(I18n.t('time.starts_at'), I18n.t('errors.messages.start_date_not_before_end_date')) if starts_at && starts_at && ends_at < starts_at + end # Scope definitions. We implement all Filterrific filters through ActiveRecord # scopes. In this example we omit the implementation of the scopes for brevity. @@ -72,6 +72,19 @@ def start_before_end_date where("user_id = ?",user_id) if user_id } + scope :other_to, lambda { |event_id| + where("id <> ?",event_id) if event_id + } + + scope :not_approved, lambda { + where("approved is NULL OR approved = TRUE") + } + + scope :overlapping, lambda { |start, ende| + where(" (:start BETWEEN starts_at AND ends_at) + OR (:ende BETWEEN starts_at AND ends_at) + OR (:start < starts_at AND :ende > ends_at)", {start:start, ende: ende}) + } def self.options_for_sorted_by [ [(I18n.t 'sort_options.sort_name'), 'name_asc'], @@ -85,14 +98,29 @@ def self.options_for_sorted_by ] end - def self.checkVacancy(startDateTime, endDateTime, rooms) - #event = self.find_by_starts_at_and_ends_at(startDateTime, endDateTime) - #if event - # return false - #else - # return true - # logger.info startDateTime -# end - return true + def checkVacancy(rooms) + logger.info self.starts_at + logger.info self.ends_at + logger.info rooms + colliding_events = [] + unless rooms.nil? + rooms = rooms.collect{|i| i.to_i} + end + + events = Event.other_to(id).not_approved.overlapping(starts_at,ends_at) + if events.empty? + logger.info "XX" + return colliding_events + else + unless rooms.nil? + rooms_count = rooms.size + events.each do | event | + if (rooms - event.rooms.pluck(:id)).size < rooms_count + colliding_events.push(event) + end + end + end + end + return colliding_events end end diff --git a/app/models/event_suggestion.rb b/app/models/event_suggestion.rb new file mode 100644 index 00000000..11194516 --- /dev/null +++ b/app/models/event_suggestion.rb @@ -0,0 +1,24 @@ +class EventSuggestion < ActiveRecord::Base + include DateTimeAttribute + has_and_belongs_to_many :rooms + accepts_nested_attributes_for :rooms + + date_time_attribute :starts_at + date_time_attribute :ends_at + + validate :dates_cannot_be_in_the_past,:start_before_end_date + + + validates :starts_at, presence: true + validates :ends_at, presence: true + + + def dates_cannot_be_in_the_past + errors.add(I18n.t('time.starts_at'), I18n.t('errors.messages.date_in_the_past')) if starts_at < Date.today + errors.add(I18n.t('time.ends_at'), I18n.t('errors.messages.date_in_the_past')) if ends_at < Date.today + end + def start_before_end_date + errors.add(I18n.t('time.starts_at'), I18n.t('errors.messages.start_date_not_before_end_date')) if starts_at && starts_at && ends_at < starts_at + end + +end diff --git a/app/models/room.rb b/app/models/room.rb index df3136f9..875ab7c6 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -3,7 +3,7 @@ class Room < ActiveRecord::Base has_many :equipment # The plural of 'equipment' is 'equipment' has_and_belongs_to_many :properties, :class_name => 'RoomProperty' has_and_belongs_to_many :events - + has_and_belongs_to_many :event_suggestions def upcoming_events return self.events.where(['ends_at >= ?', Date.today]).order('starts_at asc') end diff --git a/app/views/event_suggestions/_form.html.erb b/app/views/event_suggestions/_form.html.erb new file mode 100644 index 00000000..fcbfef3d --- /dev/null +++ b/app/views/event_suggestions/_form.html.erb @@ -0,0 +1,59 @@ +<%= form_for @event_suggestion, :html => {:id => "sugguest-form" } do |f| %> + <% if @event_suggestion.errors.any? %> +
+
+

<%= t 'errors.template.header.other', :model => EventSuggestion.model_name.human, :count => @event_suggestion.errors.count %>

+
+
+ +
+
+ <% end %> +
+ <%= f.label model_class.human_attribute_name(:starts_at), :class => 'control-label' %> +
+
+ <%= f.date_field :starts_at_date, :class => 'form-control' %> + <%= error_span(@event_suggestion[:starts_at_date]) %> +
+
+ <%= f.time_field :starts_at_time, :class => 'form-control', value: @event_suggestion.starts_at_time.strftime('%H:%M') %> + <%= error_span(@event_suggestion[:starts_at_time]) %> +
+
+ +
+
+ <%= f.label model_class.human_attribute_name(:ends_at) , :class => 'control-label' %> +
+
+ <%= f.date_field :ends_at_date, :class => 'form-control' %> + <%= error_span(@event_suggestion[:ends_at_date]) %> +
+
+ <%= f.time_field :ends_at_time, :class => 'form-control', value: @event_suggestion.ends_at_time.strftime('%H:%M') %> + <%= error_span(@event_suggestion[:ends_at_time]) %> +
+
+
+
+ <%= f.label model_class.human_attribute_name(:rooms), :class => 'control-label' %> +
+ <%= f.select :room_ids, options_from_collection_for_select(Room.all, :id, :name, selected:@event_suggestion.rooms.map(&:id)), {}, {:class=> 'selectpicker', :id => 'selectpicker', :multiple =>'' }%> +
+ + <%= error_span(@event_suggestion[:rooms]) %> +
+
+
+ <%= f.submit nil, :class => 'btn btn-primary' %> + <%= link_to t('.cancel', :default => t("helpers.links.cancel")), + events_path, :class => 'btn btn-default' %> +
+<% end %> diff --git a/app/views/event_suggestions/edit.html.erb b/app/views/event_suggestions/edit.html.erb new file mode 100644 index 00000000..35054939 --- /dev/null +++ b/app/views/event_suggestions/edit.html.erb @@ -0,0 +1,6 @@ +

Editing event_suggestion

+ +<%= render 'form' %> + +<%= link_to 'Show', @event_suggestion %> | +<%= link_to 'Back', event_suggestions_path %> diff --git a/app/views/event_suggestions/index.html.erb b/app/views/event_suggestions/index.html.erb new file mode 100644 index 00000000..e5f4e715 --- /dev/null +++ b/app/views/event_suggestions/index.html.erb @@ -0,0 +1,33 @@ +

Listing event_suggestions

+ + + + + + + + + + + + + + + + <% @event_suggestions.each do |event_suggestion| %> + + + + + + + + + + <% end %> + +
Starts atDatetimeEnds atDatetimeStatusRoom
<%= event_suggestion.starts_at %><%= event_suggestion.ends_at %><%= event_suggestion.status %><%= event_suggestion.room_id %><%= link_to 'Show', event_suggestion %><%= link_to 'Edit', edit_event_suggestion_path(event_suggestion) %><%= link_to 'Destroy', event_suggestion, method: :delete, data: { confirm: 'Are you sure?' } %>
+ +
+ +<%= link_to 'New Event suggestion', new_event_suggestion_path %> diff --git a/app/views/event_suggestions/index.json.jbuilder b/app/views/event_suggestions/index.json.jbuilder new file mode 100644 index 00000000..596bc1b6 --- /dev/null +++ b/app/views/event_suggestions/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@event_suggestions) do |event_suggestion| + json.extract! event_suggestion, :id, :starts_at, :datetime, :ends_at, :datetime, :status, :room_id + json.url event_suggestion_url(event_suggestion, format: :json) +end diff --git a/app/views/event_suggestions/new.html.erb b/app/views/event_suggestions/new.html.erb new file mode 100644 index 00000000..c04ca4d0 --- /dev/null +++ b/app/views/event_suggestions/new.html.erb @@ -0,0 +1,5 @@ +<%- model_class = EventSuggestion -%> + +<%= render :partial => 'event_suggestions/form', locals: {model_class: model_class} %> \ No newline at end of file diff --git a/app/views/event_suggestions/show.html.erb b/app/views/event_suggestions/show.html.erb new file mode 100644 index 00000000..41489810 --- /dev/null +++ b/app/views/event_suggestions/show.html.erb @@ -0,0 +1,35 @@ +<%- model_class = EventSuggestion -%> + +
+
<%= model_class.human_attribute_name(:id) %>:
+
<%= @event_suggestion.id %>
+
<%= model_class.human_attribute_name(:rooms) %>:
+
<%= concat_rooms(@event_suggestion) %>
+
<%= model_class.human_attribute_name(:starts_at) %>:
+ <% if @event_suggestion.starts_at%> +
<%= @event_suggestion.starts_at.strftime("%d.%m.%Y %T") %>
+ <% end %> +
<%= model_class.human_attribute_name(:ends_at) %>:
+ <% if @event_suggestion.ends_at%> +
<%= @event_suggestion.ends_at.strftime("%d.%m.%Y %T")%>
+ <% end %> +
<%= model_class.human_attribute_name(:user) %>:
+
<%= @user %>
+
<%= model_class.human_attribute_name(:created_at) %>:
+
<%= @event_suggestion.created_at.strftime("%d.%m.%Y %T") %>
+
<%= model_class.human_attribute_name(:updated_at) %>:
+
<%= @event_suggestion.updated_at.strftime("%d.%m.%Y %T") %>
+
+ +<%= link_to t('.back', :default => t("helpers.links.back")), + event_suggestion_path, :class => 'btn btn-default' %> +<%= link_to t('.edit', :default => t("helpers.links.edit")), + edit_event_suggestion_path(@event_suggestion), :class => 'btn btn-default' %> + + <%= link_to t('.destroy', :default => t("helpers.links.destroy")), + event_suggestion_path(@event_suggestion), + :method => 'delete', + :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, + :class => 'btn btn-danger' %> \ No newline at end of file diff --git a/app/views/event_suggestions/show.json.jbuilder b/app/views/event_suggestions/show.json.jbuilder new file mode 100644 index 00000000..bcf1d1e5 --- /dev/null +++ b/app/views/event_suggestions/show.json.jbuilder @@ -0,0 +1 @@ +json.extract! @event_suggestion, :id, :starts_at, :datetime, :ends_at, :datetime, :status, :room_id, :created_at, :updated_at diff --git a/app/views/event_templates/_form.html.erb b/app/views/event_templates/_form.html.erb index 5147b115..3ca08a0a 100644 --- a/app/views/event_templates/_form.html.erb +++ b/app/views/event_templates/_form.html.erb @@ -49,6 +49,6 @@ <%= f.submit nil, :class => 'btn btn-primary' %> <%= link_to t('.cancel', :default => t("helpers.links.cancel")), - events_path, :class => 'btn btn-default' %> + event_templates_path, :class => 'btn btn-default' %> <% end %> diff --git a/app/views/event_templates/_list.html.erb b/app/views/event_templates/_list.html.erb index 2685f1ff..a2aca10b 100644 --- a/app/views/event_templates/_list.html.erb +++ b/app/views/event_templates/_list.html.erb @@ -23,14 +23,16 @@ <% if can? :edit, event_template%> <%= link_to t('.edit', :default => t("helpers.links.edit")), edit_event_template_path(event_template), :class => 'btn btn-default btn-xs' %> - <% end %> - <% if can? :destroy, event_template %> + <% end %> + <% if can? :destroy, event_template %> <%= link_to t('.destroy', :default => t("helpers.links.destroy")), event_template_path(event_template), :method => :delete, :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, :class => 'btn btn-xs btn-danger' %> - <% end %> + <% end %> + <%= link_to t('.create', :default => t("helpers.submit.create", :model => Event.model_name.human.titleize)), + new_event_from_template_path(event_template), :class => 'btn btn-default btn-xs' %> <% end %> diff --git a/app/views/events/_form.html.erb b/app/views/events/_form.html.erb index f8fec31e..c446b40c 100644 --- a/app/views/events/_form.html.erb +++ b/app/views/events/_form.html.erb @@ -1,4 +1,4 @@ -<%= form_for @event, :html => { :class => "form-horizontal event" } do |f| %> +<%= form_for @event, :html => { :class => "form-horizontal event", :id => "event-form" } do |f| %> <% if @event.errors.any? %>
@@ -84,6 +84,9 @@
+
+ + <%= f.submit nil, :class => 'btn btn-primary' %> <%= link_to t('.cancel', :default => t("helpers.links.cancel")), events_path, :class => 'btn btn-default' %> diff --git a/app/views/events/_list.html.erb b/app/views/events/_list.html.erb index 59f9b2bc..a18a4151 100644 --- a/app/views/events/_list.html.erb +++ b/app/views/events/_list.html.erb @@ -41,11 +41,6 @@ :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, :class => 'btn btn-xs btn-danger' %> <% end %> - <% if can? :sugguest, event%> - <%= link_to t('.sugguest', :default => t("helpers.links.sugguest")), - sugguest_event_path(event), - :class => 'btn btn-xs btn-warning' %> - <% end %> <% end %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 4a809677..7ec43e02 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -4,9 +4,8 @@
-
<%= t('events.show.id') %>:
-
<%= @event.id %>
-
<%= t('events.show.name') %>:
+
<%= model_class.human_attribute_name(:id) %>:
<%= @event.id %>
+
<%= model_class.human_attribute_name(:name) %>:
<%= @event.name %>
<%= t('events.show.description') %>:
<%= raw nl2br h @event.description %>
@@ -17,11 +16,11 @@
<%= t('events.show.private') %>:
<%= check_box_tag 'private', 'private', @event.is_private, disabled: :disabled %>
<%= t('events.show.starts_at') %>:
- <% if @event.starts_at %> + <% if @event.starts_at %>
<%= @event.starts_at.strftime("%d.%m.%Y - %H:%M") %>
- <% end %> + <% end %>
<%= t('events.show.ends_at') %>:
- <% if @event.ends_at%> + <% if @event.ends_at%>
<%= @event.ends_at.strftime("%d.%m.%Y - %H:%M") %>
<% end %>
<%= t('events.show.user') %>:
@@ -36,13 +35,13 @@ <%= link_to t('.back', :default => t("helpers.links.back")), events_path, :class => 'btn btn-default' %> -<% if can? :edit, @event %> +<% if can? :edit, @event %> <%= link_to t('.edit', :default => t("helpers.links.edit")), edit_event_path(@event), :class => 'btn btn-default' %> <% end %> <%= link_to t('.create', :default => t("helpers.submit.create", :model => EventTemplate.model_name.human.titleize)), new_event_template_from_event_path(@event), :class => 'btn btn-default' %> -<% if can? :destroy, @event %> +<% if can? :destroy, @event %> <%= link_to t('.destroy', :default => t("helpers.links.destroy")), event_path(@event), :method => 'delete', diff --git a/app/views/events/show.json.jbuilder b/app/views/events/show.json.jbuilder index 8a823314..91f0ce46 100644 --- a/app/views/events/show.json.jbuilder +++ b/app/views/events/show.json.jbuilder @@ -1 +1 @@ -json.extract! @event, :id, :name, :description, :participant_count, :starts_at, :ends_at, :created_at, :updated_at, :is_private, :user_id +json.extract! @event, :id, :name, :description, :participant_count, :starts_at, :ends_at, :created_at, :updated_at, :is_private, :user_id, :rooms diff --git a/app/views/events/sugguest.html.erb b/app/views/events/sugguest.html.erb index 8c30f36a..9af985b1 100644 --- a/app/views/events/sugguest.html.erb +++ b/app/views/events/sugguest.html.erb @@ -3,7 +3,7 @@

<%=t '.title', :default => [:'helpers.titles.sugguest', 'Sugguest %{model}'], :model => model_class.model_name.human.titleize %>

-<%= form_for @event, :url => {action: "create_suggestion"}, :method => "patch", :html => { :class => "form-horizontal event" } do |f| %> +<%= form_for @event, :url => {action: "create_suggestion"}, :method => "patch", :remote => true, :html => { :class => "form-horizontal event", :id => "sugguest-form" } do |f| %> <% if @event.errors.any? %>
@@ -18,7 +18,7 @@
<% end %> - + <%= f.hidden_field :id, :value => @event.id%>
<%= f.label model_class.human_attribute_name(:name), :class => 'control-label' %>
@@ -74,9 +74,9 @@
- - <%= f.submit t('.sugguest', :default => t("helpers.links.sugguest")), :class => 'btn btn-primary' %> + <%= f.submit t('.sugguest', :default => t("helpers.links.sugguest")), :class => 'btn btn-primary', :id => "submit_suggestion" %> <%= link_to t('.cancel', :default => t("helpers.links.cancel")), events_path, :class => 'btn btn-default' %> + <%= link_to t('.sugguest', :default => t("helpers.links.sugguest")), sugguest_event_path(17), :target => "_blank" %> <% end %> diff --git a/app/views/events_approval/index.html.erb b/app/views/events_approval/index.html.erb index 3831f35c..a94d3494 100644 --- a/app/views/events_approval/index.html.erb +++ b/app/views/events_approval/index.html.erb @@ -5,6 +5,7 @@ Event Bookings + Aktion @@ -13,6 +14,7 @@ Max Mustermann requests
+ <%= link_to event.name, event_path(event) %> - <%= event.description %> (<%= event.participant_count %>) @@ -30,8 +32,8 @@ <% end %> - <%= link_to "Decline", decline_event_path(event, date: @date), :method => :decline, :class => 'btn btn-danger btn-xs' %> - <%= link_to "Approve", approve_event_path(event, date: @date), :method => :approve, :class => 'btn btn-success btn-xs' %> + + <%= link_to t('.approve', :default => t("helpers.links.approve")), approve_event_path(event, date: @date), :method => :approve, :class => 'btn btn-success btn-xs' %> <% end %> @@ -72,4 +74,38 @@ <% if !bookings_exist %>
No approved bookings.
-<% end %> \ No newline at end of file +<% end %> + \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 454a18fd..0479c040 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -55,9 +55,9 @@
  • <%= link_to Room.model_name.human.pluralize, rooms_path %>
  • <%= link_to Group.model_name.human.pluralize, groups_path %>
  • <%= link_to Task.model_name.human.pluralize, tasks_path %>
  • -
  • <%= link_to "Booking", "bookings/new" %>
  • -
  • <%= link_to "Filtering", "rooms/list" %>
  • -
  • <%= link_to "Process Requests", "events_approval/" %>
  • +
  • <%= link_to Booking.model_name.human.pluralize, new_booking_path %>
  • +
  • <%= link_to "Filtering", rooms_list_path %>
  • +
  • <%= link_to "Process Requests", events_approval_path %>