記述追加 app\views\calendars\host.html.erb(60行目)
カンマを忘れないようにして下さい。
, dayRender: function(date, cell) { <% if !@rooms.blank? %> cell.append('<span class="day-price">' + '<%= @room.price %>円' + '</span>') <% end %> }
app\views\calendars\host.html.erb
コードをコピーしてファイルを置き換えてください。
<% if !@rooms.blank? %> <div class="row"> <%= search_form_for @search, class: 'form-group', remote: true, url: host_calendar_path do |f| %> <div class="col-md-6 select"> <div class="form-group"> <label>登録しているお部屋</label> <%= f.select :room_id, options_for_select(@rooms.collect {|u| [u.listing_name, u.id]}, params[:room_id]), {}, { onchange: "$(this.form).submit()", class: "form-control" } %> </div> </div> <%= f.hidden_field :start_date, id: "start-date", value: params[:start_date], onchange: "$(this.form).submit()" %> <% end %> </div> <% end %> <div id="calendar"></div> <script> window.reservations = <%= raw @events.to_json %> console.log(reservations); function showReservations(data) { return data.map(function (e) { if (e['start_date'] !== e['end_date']) { e['end_date'] = moment.utc(e['end_date']).add(1, 'days') } return { name: e.fullname, start: e['start_date'], end: e['end_date'], avatar: e.image, status: e.status } }) } $('#calendar').fullCalendar({ header: { left: 'title', center: '', right: 'prev,next' }, defaultDate: $('#start-date').val(), events: showReservations(reservations), eventRender: function(event, element, view) { return $(` <a class="fc-day-grid-event fc-h-event fc-event fc-start fc-end"> <div class="fc-content ${event.status}"> <span class="fc-title"><img class="img-circle avatar-small" src="${event.avatar}"> ${event.name}</span> </div> </a> `); }, dayRender: function(date, cell) { <% if !@rooms.blank? %> cell.append('<span class="day-price">' + '<%= @room.price %>円' + '</span>') <% end %> } }); $('.fc-prev-button').click(function() { var current = new Date($('#start-date').val()); var prev = new Date(current.getFullYear(), current.getMonth() - 1, 1) $('#start-date').val(moment(prev).format('YYYY-MM-DD')) $('#start-date').trigger('change') }); $('.fc-next-button').click(function() { var current = new Date($('#start-date').val()); var next = new Date(current.getFullYear(), current.getMonth() + 1, 1) $('#start-date').val(moment(next).format('YYYY-MM-DD')) $('#start-date').trigger('change') }); </script>
コマンド
rails g model Calendar day:date price:bigint status:bigint room:references
コマンド マイグレーション
rails db:migrate
記述追加 app\models\room.rb
10行目に「has_many :calendars」の記述追加
class Room < ApplicationRecord # instant: {承認制: 0, すぐに予約: 1} enum instant: {Request: 0, Instant: 1} belongs_to :user has_many :photos has_many :reservations has_many :guest_reviews has_many :calendars geocoded_by :address after_validation :geocode, if: :address_changed? validates :home_type, presence: true validates :room_type, presence: true validates :accommodate, presence: true validates :bed_room, presence: true validates :bath_room, presence: true def cover_photo(size) if self.photos.length > 0 self.photos[0].image.url(size) else "blank.jpg" end end def average_rating guest_reviews.count == 0 ? 0 : guest_reviews.average(:star).round(2).to_i end end
app\models\calendar.rb
class Calendar < ApplicationRecord # status: [:利用可能, :利用不可] enum status: [:Available, :Not_Available] validates :day, presence: true belongs_to :room end
記述追加 config\routes.rb
33行目に「resources :calendars」の記述追加
Rails.application.routes.draw do #ルートをpages#homeに設定 root 'pages#home' get 'pages/home' get '/your_trips' => 'reservations#your_trips' get '/your_reservations' => 'reservations#your_reservations' get 'search' => 'pages#search' get 'dashboard' => 'dashboards#index' get '/host_calendar' => "calendars#host" resources :users, only: [:show] do member do post '/verify_phone_number' => 'users#verify_phone_number' patch '/update_phone_number' => 'users#update_phone_number' end end resources :rooms, except: [:edit] do member do get 'listing' get 'pricing' get 'description' get 'photo_upload' get 'amenities' get 'location' get 'preload' get 'preview' end resources :photos, only: [:create, :destroy] resources :reservations, only: [:create] resources :calendars end resources :guest_reviews, only: [:create, :destroy] resources :host_reviews, only: [:create, :destroy] resources :reservations, only: [:approve, :decline] do member do post '/approve' => "reservations#approve" post '/decline' => "reservations#decline" end end devise_for :users, path: '', path_names: {sign_in: 'login', sign_out: 'logout', edit: 'profile', sign_up: 'registration'}, controllers: {registrations: 'registrations'} # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end
「app\views\calendars」フォルダに「_form.html.erb」ファイルを新規作成して下さい。
app\views\calendars\_form.html.erb(新規作成したファイル)
<div class="modal fade" id="new_calendar"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button class="close" data-dismiss="modal">×</button> </div> <div class="modal-body"> <%= form_for [@room, Calendar.new] do |f| %> <div class="row"> <div class="col-md-6"> <div class="form-group"> <%= f.text_field :start_date, readonly: true, value: Date.today, class: "form-control datepicker" %> </div> </div> <div class="col-md-6"> <div class="form-group"> <%= f.text_field :end_date, readonly: true, value: Date.today, class: "form-control datepicker" %> </div> </div> </div> <div class="form-group"> <div class="btn-group" data-toggle="buttons-radio"> <div>利用可能 利用不可</div> <%= f.collection_radio_buttons :status, Calendar.statuses, :first, :first, checked: Calendar.statuses.first do |b| b.radio_button + b.label { if (b = "Available") " " elsif(b = "Not_Available") " " end } end %> </div> </div> <div class="row new-pricing"> <div class="col-md-3"> <div class="form-group"> <div class="input-group"> <span class="input-group-addon">1泊</span> <%= f.text_field :price, class: "form-control", value: @room.price, required: true %> </div> </div> </div> <div class="col-md-2"> <p style="margin-top: 10px">円</p> </div> </div> <div class="no-pricing hide"> </div> <div class="form-group"> <%= f.button "保存する", type: :submit, class: "btn btn-success" %> </div> <% end %> </div> </div> </div> </div> <script> $(function() { var notAvailable = $('#calendar_status_not_available'), available = $('#calendar_status_available'); notAvailable.click(function() { $('.no-pricing').show(); $('.new-pricing').hide(); }); available.click(function() { $('.no-pricing').hide(); $('.new-pricing').show(); }); }) </script>
記述追加 app\views\calendars\host.html.erb
19行目に「<%= render 'form' %>」の記述追加
また、複数選択した時に日付が反映されるようにスクリプトを変えています。
コードをコピーしてファイルを置き換えてください。
<% if !@rooms.blank? %> <div class="row"> <%= search_form_for @search, class: 'form-group', remote: true, url: host_calendar_path do |f| %> <div class="col-md-6 select"> <div class="form-group"> <label>登録しているお部屋</label> <%= f.select :room_id, options_for_select(@rooms.collect {|u| [u.listing_name, u.id]}, params[:room_id]), {}, { onchange: "$(this.form).submit()", class: "form-control" } %> </div> </div> <%= f.hidden_field :start_date, id: "start-date", value: params[:start_date], onchange: "$(this.form).submit()" %> <% end %> </div> <% end %> <%= render 'form' %> <div id="calendar"></div> <script> window.reservations = <%= raw @events.to_json %> window.days = <%= raw @days.to_json %> function showReservations(data) { return data.map(function (e) { if (e['start_date'] !== e['end_date']) { e['end_date'] = moment.utc(e['end_date']).add(1, 'days') } return { name: e.fullname, start: e['start_date'], end: e['end_date'], avatar: e.image, status: e.status } }) } $('#calendar').fullCalendar({ header: { left: 'title', center: '', right: 'prev,next' }, defaultDate: $('#start-date').val(), events: showReservations(reservations), eventRender: function(event, element, view) { return $(` <a class="fc-day-grid-event fc-h-event fc-event fc-start fc-end"> <div class="fc-content ${event.status}"> <span class="fc-title"><img class="img-circle avatar-small" src="${event.avatar}"> ${event.name}</span> </div> </a> `); }, dayRender: function(date, cell) { var dayInfo = $.grep(days, function(e) { return e.day === date.format(); }); console.log(dayInfo); <% if !@rooms.blank? %> if (dayInfo.length > 0) { if (dayInfo[0].status == "Not_Available") { cell.addClass('fc-past'); } else { cell.append('<span class="day-price">' + dayInfo[0].price + '円</span>') } } else { cell.append('<span class="day-price">' + '<%= number_to_currency(@room.price) %>' + '</span>') } <% end %> }, selectable: true, select: function(start, end, jsEvent, view) { var start_date = moment(start); var end_date = moment(end).subtract(1, "days"); <% if @rooms.blank? %> $('#calendar').fullCalendar('unselect'); <% end %> var overlap = reservations.filter(function(e) { var r_start_date = moment(e.start_date); var r_end_date = moment(e.end_date).subtract(1, "days"); return (r_start_date.isSameOrBefore(end_date) && r_end_date.isSameOrAfter(start_date)) }).length > 0; if(start.isBefore(moment()) || overlap) { $('#calendar').fullCalendar('unselect') } else { $('#new_calendar').modal('show'); $('#calendar_start_date').datepicker({ dateFormat: "yy-mm-dd", setDate: start_date }); $('#calendar_start_date').val(start_date.format("YYYY-MM-DD")); $('#calendar_end_date').datepicker({ dateFormat: "yy-mm-dd", setDate: end_date }); $('#calendar_end_date').val(end_date.format("YYYY-MM-DD")); } } }); $('.fc-prev-button').click(function() { var current = new Date($('#start-date').val()); var prev = new Date(current.getFullYear(), current.getMonth() - 1, 1) $('#start-date').val(moment(prev).format('YYYY-MM-DD')) $('#start-date').trigger('change') }); $('.fc-next-button').click(function() { var current = new Date($('#start-date').val()); var next = new Date(current.getFullYear(), current.getMonth() + 1, 1) $('#start-date').val(moment(next).format('YYYY-MM-DD')) $('#start-date').trigger('change') }); </script>
記述更新 app\controllers\calendars_controller.rb
3行目からの「create()」メソッド追加、38, 51, 55行目の記述を追加しています。
コードをコピーしてファイルを置き換えてください。
class CalendarsController < ApplicationController before_action :authenticate_user! include ApplicationHelper def create date_from = Date.parse(calendar_params[:start_date]) date_to = Date.parse(calendar_params[:end_date]) (date_from..date_to).each do |date| calendar = Calendar.where(room_id: params[:room_id], day: date) if calendar.present? calendar.update_all(price: calendar_params[:price], status: calendar_params[:status]) else Calendar.create( room_id: params[:room_id], day: date, price: calendar_params[:price], status: calendar_params[:status] ) end end redirect_to host_calendar_path end def host @rooms = current_user.rooms params[:start_date] ||= Date.current.to_s params[:room_id] ||= @rooms[0] ? @rooms[0].id : nil if params[:q].present? params[:start_date] = params[:q][:start_date] params[:room_id] = params[:q][:room_id] end @search = Reservation.ransack(params[:q]) if params[:room_id] @room = Room.find(params[:room_id]) start_date = Date.parse(params[:start_date]) first_of_month = (start_date - 1.months).beginning_of_month # 最初の月の1日から end_of_month = (start_date + 1.months).end_of_month # => 3ヶ月後の31日まで @events = @room.reservations.joins(:user) .select('reservations.*, users.fullname, users.image, users.email, users.uid') .where('(start_date BETWEEN ? AND ?) AND status <> ?', first_of_month, end_of_month, 2) @events.each{ |e| e.image = avatar_url(e) } @days = Calendar.where("room_id = ? AND day BETWEEN ? AND ?", params[:room_id], first_of_month, end_of_month) else @room = nil @events = [] @days = [] end end private def calendar_params params.require(:calendar).permit([:price, :status, :start_date, :end_date]) end end
記述追加 app\views\calendars\host.js.erb
2行目に「days = <%= raw @days.to_json %>」の記述を追加しています。
reservations = <%= raw @events.to_json %> days = <%= raw @days.to_json %> $('#calendar').fullCalendar('removeEvents'); $('#calendar').fullCalendar('addEventSource', showReservations(reservations)); $('#calendar').fullCalendar('rerenderEvents');
ブラウザ確認
http://localhost:3000/host_calendar
カレンダーで複数選択時の料金変更、利用不可の設定ができるようになっています。
複数選択も可能です。