学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

【民泊5.1】【Windows】カレンダーの改良

記述追加 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">&times;</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


カレンダーで複数選択時の料金変更、利用不可の設定ができるようになっています。

複数選択、料金変更、利用不可
複数選択、料金変更、利用不可


複数選択も可能です。

複数選択も可能
複数選択も可能