E110 ユーザースケジュール予約機能の実装

カレンダー形式でスケジュールが表示できるようになったので、今回はスケジュールの予約機能の実装方法を紹介します。

ユーザースケジュール予約機能作成

①スケジュール詳細表示用のモーダルを作成する。

ここではBreezeで導入したモーダルコンポーネントを利用してモーダルウインドウを作成します。

resources/views/reserve-modal.blade.php
{{-- 予約モーダル --}}
    <button type="submit" id="user-reserve-modal-button" class='hidden'
        x-data=""
        x-on:click.prevent="$dispatch('open-modal', 'user-reserve-modal')"
    >予約
    </button>

    <x-modal name="user-reserve-modal">
        <form method="post" action="{{ route('reserve.add') }}" class="p-6">
            @csrf
            @method('post')

            <h2 class="text-lg font-medium text-gray-900">
                スケジュール予約
            </h2>

            <p class="mt-1 text-sm text-gray-600">

            </p>

            <div class="mt-6">
                <input type="hidden" id="schedule_id" value="" name="schedule_id">
                <input type="hidden" id="user_id" value="{{ Auth::user()->id }}" name="user_id">

                <p>タイトル:<span class="" id="modal-data-title"></span></p>
                <p>開始日時:<span class="" id="modal-data-start"></span></p>
                <p>終了日時:<span class="" id="modal-data-end"></span></p>
                <p>スタッフ:<span class="" id="modal-data-staff"></span></p>
                <p>上限人数:<span class="" id="modal-data-limit"></span></p>
            </div>

            <div class="mt-6 flex justify-end">
                <x-secondary-button x-on:click="$dispatch('close')">
                    {{ __('Cancel') }}
                </x-secondary-button>

                <x-primary-button class="ml-3">
                    予約
                </x-primary-button>
            </div>
        </form>
    </x-modal>

②Viewファイルをインクルードする。

※下記、L25を参照

resources/views/calendar.blade.php
+    @include('reserve-modal');
  </main>

③カレンダーのスケジュールをクリックしたときにスケジュール詳細モーダルを表示する処理を実装する。

※下記、参照箇所

resources/js/calendar.js
// イベントクリック時の処理
        eventClick: function(info) {
            //alert('Event: ' + info.event.title);
            //console.log(info.event.extendedProps);

            //モーダルへのデータセット
            document.getElementById("schedule_id").value = info.event.id;
            document.getElementById("modal-data-title").innerHTML = info.event.title;
            document.getElementById("modal-data-start").innerHTML = info.event.start;
            document.getElementById("modal-data-end").innerHTML   = info.event.end;
            document.getElementById("modal-data-staff").innerHTML = info.event.extendedProps.staff;
            document.getElementById("modal-data-limit").innerHTML = info.event.extendedProps.limit;

            //モーダルを表示
            let modal_button = document.getElementById("user-reserve-modal-button");
            modal_button.click();
        },

④予約登録用のルーティングを追加する。

※下記、L35を参照

routes/web.php
  Route::middleware('auth')->group(function () {
      // プロフィールページ
      Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
      Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
      Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
+     Route::get('/calendar', [EventController::class, 'index'])->name('event.index');
    //中略
  });

⑤EventControllerに予約登録の処理を実装する。

※下記、L18~L60を参照

app/Http/Controllers/EventController.php
  class EventController extends Controller
  {
  //中略
+    public function addReserve(Request $request)
+        {
+            // バリデーション
+            $request->validate([
+                'schedule_id' => 'required|integer|exists:schedules,id',
+                'user_id' => 'required|integer|exists:users,id'
+            ]);
+    
+           try {
+                // スケジュールデータを取得
+                $schedule = Schedule::find($request->input('schedule_id'));
+    
+                // スケジュールに紐付く予約情報を取得
+                $reserves = $schedule->reserves;
+    
+                // 予約数上限を取得、ない場合は99とする
+                $limit = $schedule->reservation_limit ?? 99;
+    
+                // すでに予約済みかどうか確認
+                $reserved_count = $reserves->where('user_id', $request->input('user_id'))->count();
+                if ($reserved_count > 0) {
+                    // すでに予約済みなら終了
+                    return redirect()->route('event.index')->with('message', 'already reserve!');
+                }
+    
+                // 現在の予約数が予約上限以下であれば予約を行なう
+                if ($limit > $reserves->count()) {
+                    $reserve = new Reserve();
+                    $reserve->schedule_id = $request->input('schedule_id');
+                    $reserve->user_id = $request->input('user_id');
+                    $reserve->save();
+    
+                    // 予約完了メールの送信
+                    $this->sendReserveMail($reserve);
+    
+                    return redirect()->route('event.index')->with('message', 'add reserve!');
+                } else {
+                    return redirect()->route('event.index')->with('message', 'reserve limited!');
+                }
+            } catch (Exception $e) {
+                Log::error($e->getMessage());
+            }
+        }
  }

⑥予約登録時にユーザーに予約完了メールを送信するために、メール送信処理を実装する。

送信メールのクラスを作成します。

$sail artisan make:mail UserReserved

下記のように表示されたら成功。

INFO  Mailable [app/Mail/UserReserved.php] created successfully. 

生成されたファイルを下記のように変更します。

app/Mail/UserReserved.php
  namespace App\Mail;
  
+  use App\Models\Reserve;
   use Illuminate\Bus\Queueable;
   use Illuminate\Contracts\Queue\ShouldQueue;
   use Illuminate\Mail\Mailable;
   use Illuminate\Mail\Mailables\Content;
   use Illuminate\Mail\Mailables\Envelope;
+  use Illuminate\Mail\Mailables\Address;
   use Illuminate\Queue\SerializesModels;
  
   class UserReserved extends Mailable
   {
-      use Queueable, SerializesModels;
+      use Queueable;
+      use SerializesModels;
+  
+      public $reserve;
  
       /**
        * Create a new message instance.
+       *
+       * @return void
        */
-      public function __construct()
+      public function __construct(Reserve $reserve)
       {
-          //
+          $this->reserve = $reserve;
       }
  
       /**
        * Get the message envelope.
+       *
+       * @return \Illuminate\Mail\Mailables\Envelope
        */
-      public function envelope(): Envelope
+      public function envelope()
       {
           return new Envelope(
-              subject: 'User Reserved',
+              from: new Address('no-reply@example.com', '簡易予約アプリ'),
+              subject: 'スケジュール予約完了のお知らせ',
           );
       }
  
       /**
        * Get the message content definition.
+       *
+       * @return \Illuminate\Mail\Mailables\Content
        */
-      public function content(): Content
+      public function content()
       {
           return new Content(
-              view: 'view.name',
+              view: 'emails.reserved-user',
           );
       }
  
       /**
        * Get the attachments for the message.
        *
-       * @return array<int, \Illuminate\Mail\Mailables\Attachment>
+       * @return array
        */
-      public function attachments(): array
+      public function attachments()
       {
           return [];
       }
-  }
+  }

⑦送信メールのテンプレートファイルを作成する。

resources/views/emails/reserved-user.blade.phpを新規作成します。

resources/views/emails/reserved-user.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
</head>
<body>
    <h1>
        スケジュールを予約しました。
    </h1>
    <p>
        {{ $reserve->user->name }}
    </p>
    <p>
        スケジュールの予約が完了しました。
    </p>
    <p>
        予約番号:{{ $reserve->id }} <br>
        タイトル:{{ $reserve->schedule->title }} <br>
        日時:{{ $reserve->schedule->from_date }}{{ $reserve->schedule->due_date }} <br>
        スタッフ:{{ $reserve->schedule->staff_name }}
    </p>

</body>
</html>

⑧メール送信処理をEventControllerに実装し、addReserve()から呼び出すようにする。

※下記、L23~L66を参照

app/Http/Controllers/EventController.php
  class EventController extends Controller
  {
      // 中略
   public function getEvents()
   {
          // 中略
   }
  
+  public function addReserve(Request $request)
+      {
+          // バリデーション
+          $request->validate([
+              'schedule_id' => 'required|integer|exists:schedules,id',
+              'user_id' => 'required|integer|exists:users,id'
+          ]);
+  
+          try {
+              // スケジュールデータを取得
+              $schedule = Schedule::find($request->input('schedule_id'));
+  
+              // スケジュールに紐付く予約情報を取得
+              $reserves = $schedule->reserves;
+  
+              // 予約数上限を取得、ない場合は99とする
+              $limit = $schedule->reservation_limit ?? 99;
+  
+              // すでに予約済みかどうか確認
+              $reserved_count = $reserves->where('user_id', $request->input('user_id'))->count();
+              if ($reserved_count > 0) {
+                  // すでに予約済みなら終了
+                  return redirect()->route('event.index')->with('message', 'already reserve!');
+              }
+  
+              // 現在の予約数が予約上限以下であれば予約を行なう
+              if ($limit > $reserves->count()) {
+                  $reserve = new Reserve();
+                  $reserve->schedule_id = $request->input('schedule_id');
+                  $reserve->user_id = $request->input('user_id');
+                  $reserve->save();
+  
+                  // 予約完了メールの送信
+                  $this->sendReserveMail($reserve);
+  
+                  return redirect()->route('event.index')->with('message', 'add reserve!');
+              } else {
+                  return redirect()->route('event.index')->with('message', 'reserve limited!');
+              }
+          } catch (Exception $e) {
+              Log::error($e->getMessage());
+          }
+      }
+  
    }

⑨予約登録時に管理者にも予約完了メールを送信する処理を実装する。

$sail artisan make:mail AdminReserved

※下記の様に表示されたら成功。

$INFO  Mailable [app/Mail/AdminReserved.php] created successfully. 

⑩ユーザー宛と同様に送信メールのテンプレートファイルを作成し、メールクラスを設定する。

resources/views/emails/reserved-admin.blade.php を新規作成します。

resources/views/emails/reserved-admin.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
</head>
<body>
    <h1>
        スケジュールが予約されました。
    </h1>
    <p>
        予約番号:{{ $reserve->id }} <br>
        ユーザー:{{ $reserve->user->name }}<br>
        タイトル:{{ $reserve->schedule->title }} <br>
        日時:{{ $reserve->schedule->from_date }}{{ $reserve->schedule->due_date }} <br>
        スタッフ:{{ $reserve->schedule->staff_name }}
    </p>

</body>
</html>

管理者宛メール送信処理をEventController->sendReserveMail()に追加実装する。

※下記、L23~L31を参照

app/Http/Controllers/EventController.php
  class EventController extends Controller
  {
      // 中略
      public function addReserve(Request $request)
      {
          // 中略
      }
  
+    private function sendReserveMail(Reserve $reserve)
+    {
+        //send user email
+        Mail::to($reserve->user->email)->send(new UserReserved($reserve));
+
+        //send admin email
+        $admin_user = Admin::first();
+        Mail::to($admin_user->email)->send(new AdminReserved($reserve));
+    }
  }

確認事項

・ユーザーのカレンダーページからスケジュールをクリックできるかを確認する。

・スケジュールの詳細がモーダルで表示できるかを確認する。

・モーダルで予約ボタンをクリックした時に予約が完了し、ユーザーのメールアドレス宛に予約完了メールが送信されるかを確認する。

・管理者のメールアドレス宛も予約完了メールが送信されるかを確認する。

・管理画面予約管理に予約した情報が表示されかを確認する。

---