🦏
予約システム 再
参考にさせていただいた記事
Gem
"simple_calendar"をインストール
gem "simple_calendar", "~> 2.0"
stylesheets/application.css
*= require simple_calendar
追加する
コントローラー作成(一覧のみ記述)
rails g controller reservations
def index
@reservations = Reservation.all.where("day >= ?", Date.current).where("day < ?", Date.current >> 3).order(day: :desc)
end
あとでコントローラーは追記する
モデル作成
rails g model reservation
db/migrate/xxxxx_create_reservations_rb
class CreateReservations < ActiveRecord::Migration[6.1]
def change
create_table :reservations do |t|
t.date "day", null: false
t.string "time", null: false
t.bigint "user_id", null: false
t.datetime "start_time", null: false
t.index ["user_id"], name: "index_reservations_on_user_id"
t.timestamps
end
end
end
ルーティング
config/routes.rb
resources :reservations, only: [:index, :new, :create, :show, :edit, :update, :destroy]
一番下に記述(単独でいい。)
ビュー作成
rails g simple_calendar:views
↓ 結果
create app/views/simple_calendar
create app/views/simple_calendar/_calendar.html.erb
create app/views/simple_calendar/_month_calendar.html.erb
create app/views/simple_calendar/_week_calendar.html.erb
views/reservations/index.erb
<div class="container">
<div class="row">
<div class="col-12 text-center mt-5">
<h1>予約画面</h1>
<p>3ヶ月先まで予約することができます。</p>
</div>
<div class="col-12 mt-3">
<%= week_calendar events: @reservations do |date, reservations| %>
<%= date.day %>
<% end %>
</div>
</div>
</div>
時間帯を追加する
時間帯の配列を作成
app/helpers/reservations_helper.rb
module ReservationsHelper
def times
times = ["9:00",
"9:30",
"10:00",
"10:30",
"11:00",
"11:30",
"13:00",
"13:30",
"14:00",
"14:30",
"15:00",
"15:30",
"16:00",
"16:30"]
end
end
views/simple_calender/_week_calender.html.erb
<div class="simple-calendar">
<div class="calendar-heading">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: 'Week') %> <%= calendar.week_number %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: 'Week') %> <%= calendar.week_number %> - <%= calendar.end_week %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view %>
</div>
<table class="table table-striped">
<thead>
<tr>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
<% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
<% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
<% else %>
<% passed_block.call day, sorted_events.fetch(day, []) %>
<% end %>
<% end %>
<% end %>
</tr>
< !-- ここから -->
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td></td>
<% end %>
</tr>
<% end %>
<!-- ここまで -->
<% end %>
</tbody>
</table>
</div>
- helperで定義した
times
メソッドを使用して時間帯を表示する。
予約を取得する
models/reservation.rb
class Reservation < ApplicationRecord
def self.reservations_after_three_month
reservations = Reservation.all.where("day >= ?", Date.current).where("day < ?", Date.current >> 3).order(day: :desc)
reservation_data = []
reservations.each do |reservation|
reservations_hash = {}
reservations_hash.merge!(day: reservation.day.strftime("%Y-%m-%d"), time: reservation.time)
reservation_data.push(reservations_hash)
end
reservation_data
end
end
-
reservationモデル
にreservationsテーブル
から予約を取得するメソッドを作成する。 - 取得する際にDBアクセスを減らすために配列に必要なデータを追加している。
-
day
とtime
は予約状況の確認で必要になる。
app/helpers/reservations_helper.rb
def check_reservation(reservations, day, time)
start_time = DateTime.parse("#{day} #{time} JST")
reservations.any? { |reservation| reservation.start_time == start_time }
end
- 予約の有無によって画面の表示を変更する
views/simple_calender/_week_calender.html.erb
<div class="simple-calendar">
<div class="calendar-heading">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: 'Week') %> <%= calendar.week_number %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: 'Week') %> <%= calendar.week_number %> - <%= calendar.end_week %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view %>
</div>
<table class="table table-striped">
<thead>
<tr>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
<% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
<% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
<% else %>
<% passed_block.call day, sorted_events.fetch(day, []) %>
<% end %>
<% end %>
<% end %>
</tr>
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td>
<!--ここから--->
<% if check_reservation(@reservations, day, time) %>
<%= 'x' %>
<% else %>
<%= 'o' %>
<% end %>
<!--ここまで--->
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
-
reservations_after_three_monthメソッド
を使用して、予約データを取得する -
reservations_after_three_monthメソッド
の返り値をcheck_reservationメソッド
の引数にする。 -
true
の場合は予約がある → "x" -
false
の場合は予約がない → "o"
※ 予約がない状態なので、全て丸
"o"をクリックして、新規予約できるようにする
新規予約画面を作成する(new.html.erb)
views/reservations/new.html.erb
<div class="container">
<div class="row justify-content-center">
<div class="col-12 text-center title mt-5">
<h1>新規予約画面</h1>
</div>
<div class="col-6 mt-3 content text-center">
<%= form_with model: @reservation, local: true, class: 'form' do |form| %>
<!--%= render 'layouts/error_messages', model: form.object %>-->
<div class="day form-group ">
<%= form.label :day, '日付' %>
<%= form.text_field :day, class: 'form-control', value: @day, readonly: true %>
</div>
<div class="time form-group">
<%= form.label :time, '時間' %>
<%= form.text_field :time, class: 'form-control', value: @time, readonly: true %>
</div>
<%= form.hidden_field :user_id, value: current_user.id %>
<%= form.hidden_field :start_time, value: @start_time %>
<div class="submit" >
<%= form.submit value: '予約する', class: 'btn btn-primary mx-auto d-block' %>
</div>
<% end %>
<div class="col-12 text-right">
<%= link_to '戻る', reservations_path %>
</div>
</div>
</div>
</div>
予約詳細画面を作成する
views/reservations/show.html.erb
<div class="container">
<div class="row justify-content-center">
<div class="col-12 text-center title mt-5">
<h1>予約詳細画面</h1>
</div>
<div class="col-6 mt-3 text-center content">
<div>
<label><strong>日付:</strong></label>
<p style="display:inline;"><%= @reservation.day %></p>
</div>
<div>
<label><strong>時間:</strong></label>
<p style="display:inline;"><%= @reservation.time %></p>
</div>
</div>
<div class="col-12 text-right">
<%= link_to '予約画面に戻る', reservations_path %>
</div>
</div>
</div>
コントローラー追加
controllers/reservations_controller.rb
def new
@reservation = Reservation.new
@day = params[:day]
@time = params[:time]
@start_time = DateTime.parse("#{@day} #{@time} JST")
end
def show
@reservation = Reservation.find(params[:id])
end
def create
@reservation = Reservation.new(reservation_params)
@reservation.start_time = Time.zone.parse(params[:reservation][:start_time])
@reservation.user_id = current_user.id
if @reservation.save
Rails.logger.debug "Reservation created successfully: #{@reservation.inspect}"
redirect_to reservation_path(@reservation.id)
else
render :new
end
end
private
def reservation_params
params.require(:reservation).permit(:day, :time, :user_id, :start_time)
end
カレンダーに追記する
- 予約がない"o"の場合に、
link_to
を使用して新規予約画面へのリンクを実装する。 -
day
とtime
をパラメーターで渡す。
_week_calender.html.erb
<div class="simple-calendar">
<div class="calendar-heading text-center p-2 mb-4" style="background-color: #f8f9fa; border-radius: 5px;">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view, class: 'btn btn-sm btn-outline-primary' %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: 'Week') %> <%= calendar.week_number %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: 'Week') %> <%= calendar.week_number %> - <%= calendar.end_week %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view, class: 'btn btn-sm btn-outline-primary' %>
</div>
<table class="table table-bordered text-center">
<thead>
<tr>
<th></th>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<th></th>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day), style: "vertical-align: top; padding: 10px;" do %>
<div>
<strong><%= day.day %></strong>
<ul class="list-unstyled">
<% sorted_events.fetch(day, []).each do |reservation| %>
<!--<li style="background-color: #fff3cd; border-radius: 5px; padding: 5px; margin-top: 5px;">-->
<!--時間: %= reservation.time %> - 予約者: %= reservation.user.name %>-->
<!--</li>-->
<% end %>
</ul>
</div>
<% end %>
<% end %>
</tr>
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td>
<% if check_reservation(@reservations, day, time) %>
<%= content_tag :span, '✖️', style: "color: red;" %>
<% else %>
<!--ここから--->
<%= link_to new_reservation_path(day: day, time: time), style: "text-decoration: none;" do %>
<span style="color: green;">◯</span>
<% end %>
<!--ここまで--->
<% end %>
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
-
一覧(レイアウト変更した)
-
"o"がまだ予約ないので、"o"をクリックすると、新規予約画面へ遷移
-
予約ボタンをクリックすると、予約が完了になる
予約をマイページに表示する
コントローラー追記
users_controller.rb
def show
@user = User.find(params[:id])
@current_entry = Entry.where(user_id: current_user.id)
@another_entry = Entry.where(user_id: @user.id)
<!--ここから--->
@user_reservations = current_user.reservations.where("start_time >= ?", DateTime.current).order(day: :desc)
@visit_histroy = current_user.reservations.where("start_time < ?", DateTime.current).where("start_time > ?", DateTime.current << 12).order(day: :desc)
<!--ここまで--->
:
:
キャンセルの為の記述(ネットからは、2日前しかできない。)
users_controller.rb
def cancel
if @reservation.user == current_user # 予約がログイン中のユーザーのものであるかを確認
if @reservation.day >= Date.today + 2.days
@reservation.update(status: 'キャンセル済み') # キャンセル処理
redirect_to user_path(current_user), notice: '予約をキャンセルしました。'
else
redirect_to user_path(current_user), alert: '2日前を過ぎた予約は電話でのみキャンセルできます。'
end
else
redirect_to root_path, alert: '他のユーザーの予約をキャンセルすることはできません。'
end
end
private
def set_reservation
@reservation = Reservation.find(params[:id])
end
reservation.rb
class ReservationsController < ApplicationController
before_action :authenticate_user!
before_action :set_reservation, only: [:show, :destroy]
def index
@reservations = Reservation.where("day >= ?", Date.current)
.where("day < ?", Date.current >> 3)
.order(day: :desc)
end
def new
@reservation = Reservation.new
@day = params[:day]
@time = params[:time]
@start_time = DateTime.parse("#{@day} #{@time} JST")
end
def show
@reservation = Reservation.find(params[:id])
end
def create
@reservation = Reservation.new(reservation_params)
@reservation.start_time = Time.zone.parse(params[:reservation][:start_time])
@reservation.user_id = current_user.id
if @reservation.save
Rails.logger.debug "Reservation created successfully: #{@reservation.inspect}"
redirect_to reservation_path(@reservation.id)
else
render :new
end
end
def today
@reservations = Reservation.where(day: Date.today) # 今日の予約を取得
end
<!--キャンセルをdestroyで定義(cancelをしたい場合は、新しくマイグレーションファイルを作成しないといけない。)--->
def destroy
if @reservation.user == current_user
if @reservation.day >= Date.today + 2.days
@reservation.destroy
redirect_to reservations_path, notice: '予約をキャンセルしました。'
else
redirect_to reservations_path, alert: '2日前を過ぎた予約は電話でのみキャンセルできます。'
end
else
redirect_to root_path, alert: '他のユーザーの予約をキャンセルすることはできません。'
end
end
private
def set_reservation
@reservation = Reservation.find(params[:id])
end
def reservation_params
params.require(:reservation).permit(:day, :time, :user_id, :start_time, menu_ids: [])
end
def check_reservation(reservations, day, time)
start_time = DateTime.parse("#{day} #{time} JST")
reservations.any? { |reservation| reservation.start_time == start_time }
end
end
cancel→canselにしてたので、注意。
ビュー追記
users/show.html.rb
<table class="table">
<h2><strong>予約一覧</strong></h2>
<thead>
<tr>
<th scope="col"><i class="fa-solid fa-scissors"></i> トリミング予約日</th>
<th></th>
</tr>
</thead>
<tbody>
<% @user_reservations.each do |user_reservation| %>
<tr>
<td>
<strong>
<i class="fa-regular fa-square-check"></i>
<%= user_reservation.day %>
<%= user_reservation.time %>
</strong>
</td>
<td>
<% if user_reservation.day >= Date.today + 2.days %>
<%= link_to 'キャンセル', cansel_reservation_path, method: :patch, data: { confirm: '本当にキャンセルしますか?' } %>
<% else %>
<p>2日前を過ぎたため、キャンセルは電話でのみ受け付けています。</p>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
トリミング予約日の横に予約したメニューがを表示できるようにしたい。
予約時にラジオボタンでメニュー選べるようにできる。
見た目が質素すぎるけど、練習のため。
おまけ
- これでできたと思ったけど、お店側が今日の予約確認できるところを作っていなかったので、作りました。
- 管理者側と会員側と分けてないアプリケーションで練習したので次は、管理者側で一覧を見れるように作る。
ルーティング追加
routes.rb
resources :reservations, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
<!--ココカラ--->
collection do
get :today
end
<!--ここまで--->
end
コントローラー追加
controllers/reservations_controller.rb
def today
@reservations = Reservation.where(day: Date.today )
#今日の予約を取得(reservationsテーブル、予約の日時に関連するカラムは dayなので)
end
ビュー作成
views/reservations/today.html.erb
<div class="container mt-5 p-4" style="background-color: #f9f9f9; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
<h3 class="mb-4 text-center" style="color: #333;">本日の予約一覧</h3>
<% if @reservations.any? %>
<div class="list-group">
<% @reservations.each do |reservation| %>
<div class="list-group-item list-group-item-action mb-3" style="border-radius: 8px; background-color: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);">
<h5 class="mb-1">ユーザー: <%= reservation.user.name %></h5>
<p class="mb-1">予約日: <%= reservation.day.strftime('%Y年%m月%d日') %></p>
<p class="mb-1">予約時間: <%= reservation.time %></p>
</div>
<% end %>
</div>
<% else %>
<p class="text-center mt-4" style="color: #666;">本日の予約はありません。</p>
<% end %>
</div>
予約システムってあと何があったら便利だろう...
メニューによって、一時間じゃ終わらない場合は、自動的に枠を増やしたいけど、それはどうやるのだろう。
トリミングサロンの予約システムで、
犬種と大型(例:12kg~)、中型(例:8kg~)、小型(例:~3kg)、シャンプーのみとか選択したら、
自動的に目安で枠の変動ができたりとか、
性格の問題でトリマーさん一人だとできない子の場合は、トリマー枠を減らすなど、
やりたいことはたくさんあるんだけど、今の頭だとどうやるんだか。
Discussion