「#laravel」タグアーカイブ

【Laravel】【ホテル予約管理】ユーザーアカウントにユーザー情報を含める

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、これからどんな形にしていこうかというと、

  • ユーザー登録時にユーザー情報を入力する(フルネーム、住所、電話番号)
  • 予約登録時にはログインユーザーの情報を使用するので、ユーザー情報の入力を省く
  • 部屋ごとに利用できる(空きのある)日と、利用できない(ほかユーザーが予約済み)日をわかるようにする

というのが必要かなと。

まずは、ユーザー登録処理を修正しましょうか。

というわけで、データベースのカラムを追加します。

$ php artisan make:migration move_column_name_address_phone_to_users_table --table=users
class MoveColumnNameAddressPhoneToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('fullname')->after('email');
            $table->string('address')->after('fullname');
            $table->string('phone')->after('address');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('fullname');
            $table->dropColumn('address');
            $table->dropColumn('phone');
        });
    }
}
$ php artisan migrate

これで、usersテーブルにフルネーム、住所、電話番号のカラムが作成されました。

Viewを書き換えます。

                        <div class="form-group{{ $errors->has('fullname') ? ' has-error' : '' }}">
                            <label for="fullname" class="col-md-4 control-label">Full Name</label>

                            <div class="col-md-6">
                                <input id="fullname" type="text" class="form-control" name="fullname" value="{{ old('fullname') }}" required autofocus>

                                @if ($errors->has('fullname'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('fullname') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('address') ? ' has-error' : '' }}">
                            <label for="address" class="col-md-4 control-label">Address</label>

                            <div class="col-md-6">
                                <input id="address" type="text" class="form-control" name="address" value="{{ old('address') }}" required autofocus>

                                @if ($errors->has('address'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('address') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('phone') ? ' has-error' : '' }}">
                            <label for="phone" class="col-md-4 control-label">phone</label>

                            <div class="col-md-6">
                                <input id="phone" type="text" class="form-control" name="phone" value="{{ old('phone') }}" required autofocus>

                                @if ($errors->has('phone'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('phone') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

register.blade.phpに上の部分を書き足しました。

登録処理も書き換えます。

app/User.phpを以下のように書き換えます。

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'fullname', 'address', 'phone', 'email', 'password',
    ];

これは、usersテーブルのカラムを設定している箇所です。

これを実際のカラム名に合わせなければ、正しくデータベースに反映されません。

次に、パラメータチェックです。

registerController.phpのバリデートルールを書き足します。

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'fullname' => 'required|string|max:255',
            'address' => 'required|string|max:255',
            'phone' => 'required|digits:11',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

そして、実際にユーザーを作る処理。

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'fullname' => $data['fullname'],
            'address' => $data['address'],
            'phone' => $data['phone'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }

これで、ユーザー情報を追加することができました。

【Laravel】【ホテル予約管理】ユーザーの権限管理を行う

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、お次の課題は、

  • 一般ユーザーからでも予約登録ができる
  • レスポンシブ対応にする

です。

で、そのために必要になるのが、ユーザーの権限管理というものです。

具体的には、管理者用のアカウントを登録して、管理者アカウントからは今までのフル機能が使用できる。

その一方で、通常ユーザーの場合は、一部の機能だけが使用できる、という感じです。

Laravelの権限管理の方法は色々あるようですが、今回は一番簡単なGATEを使用した方法を採用しようと思います。

この方法なら、新たに特別なプラグインを使用する必要がないためです。

では、Userテーブルに還元を管理するカラムroleを追加します。

$ php artisan make:migration add_column_role_users_table --table=users
class AddColumnRoleUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->tinyInteger('role')->default(0)->after('password');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }
}
$ php artisan migrate

単純にrole=0が一般ユーザー、role=1が管理者アカウントとします。

では、Seederを使用して、管理者アカウントを予め追加しておきます。

(これを行わないと、管理者権限を与えるユーザーが存在しないことになるので、運用できません!)

$ php artisan make:seeder UsersTableSeeder
class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            'name' => 'manager',
            'email' => 'manager@gmail.com',
            'password' => bcrypt('manager'),
            'role' => 1,
        ]);
    }
}
$ composer dump-autoload
$ php artisan db:seed --class=UsersTableSeeder

登録できました。

次は、AuthServiceProviderのboot()に処理を追加して、GATEによる権限の定義を記入します。

    public function boot()
    {
        $this->registerPolicies();

        Gate::define('manager', function ($user) {
            return ($user->role == 1);
        });
        Gate::define('user', function ($user) {
            return ($user->role == 0);
        });
    }

これで、role=1(manager)、role=0(user)がGATEによって定義されました。

では、Viewを書き換えて、一般ユーザーには不要なメニューを表示させないようにします。

<div class="panel-body">
    <detail-component></detail-component>
    <div>{{ Html::Link('/management/create', '追加', ['class' => 'btn']) }}</div>
</div>
@can('manager')
<div>{{ Html::link('/management/schedule', 'スケジュール', ['class' => 'btn']) }}</div>
<div>{{ Html::link('/management/total', '集計', ['class' => 'btn']) }}</div>
<div>{{ Html::link('/room', '部屋一覧へ', ['class' => 'btn']) }}</div>
<div>{{ Html::link('/management/checkout', '本日のチェックアウト', ['class' => 'btn']) }}</div>
@endcan

ポイントは@can(権限)〜@canend。

@can(権限)に権限の名前を入れることで、ユーザーの権限に対して@can(権限)〜@canendの表示を切り替えることができます。

manager権限

一般ユーザー

こんな感じで、表示の切り替えができました。

では、次回からは一般ユーザーの使い勝手がいいように修正していきましょうか。

【laravel】【ホテル予約管理】チェックアウト時刻一覧の問題を解決する

本日から本気出す。

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

実は、このリポジトリの処理には重大な問題があります。

    public function getCheckoutList()
    {
        $ret = array();
        $index = 0;
        $checkout = ReserveManagement::select('rooms.name as roomname', 'checkout')
                                    ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                    ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                    ->get();
        date_default_timezone_set('Asia/Tokyo');
        $today = date("Y-m-d");
        foreach ($checkout as $value) {
            $str = explode(" ", $value->checkout);
            if($today == $str[0]) {
                $value->checkout = $str[1];
                $ret[$index] = $value;
                $index++;
            }
        }
        return $ret;
    }

全レコードを取得したあと、日付チェックをおこない、必要なデータのみを取得する、というやり方ですが、このやり方では、大量のデータをデータベースから取得し、PHPで処理する、というやり方なので、サーバに大きな負荷がかかります。

これを解消するには、データベースから取得するデータの量を度絞らなければなりません。

なので、以下のように修正します。

    public function getCheckoutList()
    {
        date_default_timezone_set('Asia/Tokyo');
        $today = date("Y-m-d 00:00:00");
        $tomorrow = date("Y-m-d 00:00:00", strtotime("tomorrow"));
        return ReserveManagement::select('rooms.name as roomname', 'checkout')
                                    ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                    ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                    ->whereBetween('checkout', [$today, $tomorrow])
                                    ->get();
    }

クエリビルダにwhereBetween()を追加し、BETWEEN節を追加しました。範囲は、アクセス当日の0時から次の日の0時です。

これだけで必要なデータは全て取れましたので、それをそのまま返します。

動作結果は前回と同じになります。

うん、こっちのほうがコードもスッキリしていいですね。

【Laravel】【Vue.js】【ホテル予約管理】チェックアウト一覧を表示

前回までの状況はこちら

予約一覧から本日チェックアウトする部屋を探して画面に表示させます。

View

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">本日のチェックアウト時刻</div>

                <div class="panel-body">
                    <table class="checkout">
                        <tr>
                            <th class="name">部屋</th>
                            <th class="time">チェックアウト時刻</th>
                        </tr>
                    @foreach ($Lists as $list)
                        <tr>
                            <td class="name">{{ $list->roomname }}</td>
                            <td class="time">{{ $list->checkout }}</td>
                        </tr>
                    @endforeach
                    </table>
                </div>
                <div>{{ Html::link('/management', '戻る', ['class' => 'btn']) }}</div>
            </div>
        </div>
    </div>
</div>
@endsection

ルーティング

Route::get('/management/checkout', 'RegisterManagementController@checkout');

コントローラー


    /**
     * 当日のチェックアウト時刻一覧を表示
     */
    public function checkout(Request $request)
    {
        return view('register.checkout',
                        [
                            'Lists' => $this->registerManagement->getCheckoutList()
                        ]);
    }

リポジトリ

    public function getCheckoutList()
    {
        $ret = array();
        $index = 0;
        $checkout = ReserveManagement::select('rooms.name as roomname', 'checkout')
                                    ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                    ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                    ->get();
        date_default_timezone_set('Asia/Tokyo');
        $today = date("Y-m-d");
        foreach ($checkout as $value) {
            $str = explode(" ", $value->checkout);
            if($today == $str[0]) {
                $value->checkout = $str[1];
                $ret[$index] = $value;
                $index++;
            }
        }
        return $ret;
    }

ここまでアップデートする過程で、checkoutがnullになるケースがあったためか、データベースで日付指定でwhereすると、うまく行かないので、結局、全テーブルから日付を全部チェックする、という処理になりました。

もっとうまくできる方法ないかねぇ。

あと、タイムゾーンも考慮しないと、日付の判定がうまく行かないときがあるので、date()関数を使用するときは注意。

こんな感じで最終チェック後、提出します。

【Laravel】【Vue.js】【ホテル予約管理】予約の削除を実装する

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

予約詳細画面に削除ボタンを設置します。

                <button @click="closeModal">close</button>
                <button v-if="edit_flg == false" @click="onClickEdit">編集</button>
                <button v-else @click="onClickSave">保存</button>
                <button v-if="edit_flg == false" @click="onClickDelete">削除</button>
            onClickDelete: function() {
                var self = this;
                this.param.year = this.selectYear;
                this.param.month = this.selectMonth;
                this.param.room = this.selectRoom;
                this.param.id = this.contents.id;
                axios.post('/api/delete', this.param).then(function(response){
                    self.registers = [];
                    self.updateRegisters(self, response.data.registerLists);
                    self.closeModal();
                }).catch(function(error){
                    self.error_flg = true;
                    self.error_message = error.response.data.errors;
                });
            },
            updateRegisters: function(self, registerLists){
                registerLists.forEach(element => {
                    self.registers.push(
                        {
                            id:element.id, 
                            name:element.name,
                            address:element.address,
                            phone:element.phone,
                            num:element.num,
                            roomid:element.roomid,
                            room:element.room,
                            days:element.days,
                            start_day:element.start_day, 
                            checkout:element.checkout
                        }
                    );
                });
            }

APIへのルーティング

Route::post('/api/delete', 'ApiController@delete');
    public function delete(Request $request)
    {
        $this->registerManagement->deleteById($request->id);
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room
        )]);
    }

実際に削除したら、レスポンスで予約一覧を返し、一覧を最新の状態で表示してもらいます。

あとは、一覧更新処理が共通しているので関数化。

難なく設置できました。

・・・追加処理もモーダル化する?

【Laravel】【Vue.js】【ホテル予約管理】予約の編集を行う

前回までの状況はこちら

前回の予約詳細画面。

これに、編集ボタンを追加して、予約編集できるようにします。

これが思いの外大変だった。

まずはテンプレートを編集。

        <div id="overlay" v-show="showContent">
            <div id="content">
                <p v-if="error_flg == true" class="error">{{error_message}}</p>
                <table class="edit">
                    <tbody>
                        <tr>
                            <th>名前</th>
                            <td v-if="edit_flg == true"><input type="text" v-model=contents.name /></td>
                            <td v-else>{{ contents.name }}</td>
                        </tr>
                        <tr>
                            <th>住所</th>
                            <td v-if="edit_flg == true"><input type="text" v-model=contents.address /></td>
                            <td v-else>{{ contents.address }}</td>
                        </tr>
                        <tr>
                            <th>電話番号</th>
                            <td v-if="edit_flg == true"><input type="text" v-model=contents.phone /></td>
                            <td v-else>{{ contents.phone }}</td>
                        </tr>
                        <tr>
                            <th>人数</th>
                            <td v-if="edit_flg == true">
                                <select v-model=contents.num>
                                    <option v-for="num in nums" v-bind:value="num.value">{{ num.text }}</option>
                                </select>
                            </td>
                            <td v-else>{{ contents.num }}</td>
                        </tr>
                        <tr>
                            <th>宿泊部屋</th>
                            <td v-if="edit_flg == true">
                                <select v-model="contents.roomid">
                                    <option v-for="room in rooms" v-bind:value="room.id">{{ room.name }}</option>
                                </select>
                            </td>
                            <td v-else>{{ contents.room }}</td>
                        </tr>
                        <tr>
                            <th>宿泊日数</th>
                            <td v-if="edit_flg == true"><input type="number" v-model=contents.days /></td>
                            <td v-else>{{ contents.days }}</td>
                        </tr>
                        <tr>
                            <th>宿泊日</th>
                            <td v-if="edit_flg == true"><input type="date" v-model=contents.start_day /></td>
                            <td v-else>{{ contents.start_day }}</td>
                        </tr>
                        <tr>
                            <th>チェックアウト</th>
                            <td v-if="edit_flg == true">
                                <select v-model="contents.checkout">
                                    <option v-for="time in timeList" v-bind:value="time.key">{{ time.value }}</option>
                                </select>
                            <td v-else>{{ contents.checkout }}</td>
                        </tr>
                    </tbody>
                </table>
            <p>
                <button @click="closeModal">close</button>
                <button v-if="edit_flg == false" @click="onClickEdit">編集</button>
                <button v-else @click="onClickSave">保存</button>
            </p>
            </div>
        </div>

edit_flgという状態変数を見て、これがtrueならば、タグをinputに置き換える、という作りになっています。

では、データの方はというと、

        data() {
            return {
                error_message: "",
                error_flg:false,
                errors: {},

 中略

                nums: [
                    {text:'1', value:1},
                    {text:'2', value:2}
                ],
                timeList:[],
                selectRoom: 0,
                rooms: [],
                result: [],
                param: {
                    year: 2019,
                    month: 1,
                    room: 1
                },
                registers: [],
                showContent: false,
                contents: {
                    name: "",
                    address: "",
                    phone: "",
                    num: 0,
                    roomid: 0,
                    room: "",
                    days: 0,
                    start_day: "",
                    checkout: "",
                },
                edit_flg: false
            }
        },

ポイントはedit_flg。これをtrueにすることで、テンプレートが編集用に置き換わります。

なお、編集中のテンプレートは、予約追加時と同じくらいの機能を持たなくてはいけません。

もう、予約追加処理も、この中に入れてしまってもいいのかもしれない。

実際の処理は、

        created: function() {
            this.getRooms();
            this.getTimeList();
        },
        methods: {
            getRooms: function() {
                var self = this;
                axios.post('/api/rooms').then(function(response){
                    response.data.roomLists.forEach(element => {
                        self.rooms.push({id:element.id, name:element.name});
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            getRegisters: function() {
                var self = this;
                this.param.year = this.selectYear;
                this.param.month = this.selectMonth;
                this.param.room = this.selectRoom;
                axios.post('/api/registers', this.param).then(function(response){
                    self.registers = [];
                    response.data.registerLists.forEach(element => {
                        self.registers.push(
                            {
                                id:element.id, 
                                name:element.name,
                                address:element.address,
                                phone:element.phone,
                                num:element.num,
                                roomid:element.roomid,
                                room:element.room,
                                days:element.days,
                                start_day:element.start_day, 
                                checkout:element.checkout
                            }
                        );
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            onClickEdit: function(){
                this.edit_flg = true;
                var checkoutTime = this.contents.checkout.split(" ")[1];
                this.timeList.forEach(element => {
                    if(checkoutTime == element.value+":00"){
                        this.contents.checkout = element.key;
                    }
                });
            },
            onClickSave: function(){
                var self = this;
                this.param.year = this.selectYear;
                this.param.month = this.selectMonth;
                this.param.room = this.selectRoom;
                this.param.contents = this.contents;
                axios.post('/api/update', this.param).then(function(response){
                    self.registers = [];
                    response.data.registerLists.forEach(element => {
                        self.registers.push(
                            {
                                id:element.id, 
                                name:element.name,
                                address:element.address,
                                phone:element.phone,
                                num:element.num,
                                roomid:element.roomid,
                                room:element.room,
                                days:element.days,
                                start_day:element.start_day, 
                                checkout:element.checkout
                            }
                        );
                    });
                    self.closeModal();
                }).catch(function(error){
                    self.error_flg = true;
                    self.error_message = error.response.data.errors;
                });
            },
            openModal: function(id){
                for(var i = 0; i< this.registers.length; i++){
                    if(this.registers[i].id == id){
                        this.contents.id = this.registers[i].id;
                        this.contents.name = this.registers[i].name;
                        this.contents.address = this.registers[i].address;
                        this.contents.phone = this.registers[i].phone;
                        this.contents.num = this.registers[i].num;
                        this.contents.roomid = this.registers[i].roomid;
                        this.contents.room = this.registers[i].room;
                        this.contents.days = this.registers[i].days;
                        this.contents.start_day = this.registers[i].start_day;
                        this.contents.checkout = this.registers[i].checkout;
                        break;
                    }
                }
                this.showContent = true;
                this.edit_flg = false;
            },
            closeModal: function(){
                this.showContent = false;
                this.edit_flg = false;
            },
            getTimeList: function(){
                var self = this;
                axios.post('/api/timelist').then(function(response){
                    for (let [key, value] of Object.entries(response.data.timelist)){
                        self.timeList.push({key: key, value: value});
                    }
                }).catch(function(error){
                    console.log("失敗しました");
                });
            }

onClickEdit()で、詳細画面から、編集画面に切り替わります。

そして、onClickSave()で、入力したデータをLaravel側のAPI処理にデータを渡します。

うん、予約追加もこの中でいいよね。

あと、サーバ側でエラーが発生した場合のメッセージ表示も追加しています。

error_flgがtureになったらテンプレートにもその要素が表示され、API側で送られたメッセージが画面に表示されます。

では、API側の処理。

ルーティングに以下を追加します。

Route::post('/api/update', 'ApiController@update');
Route::post('/api/timelist', 'ApiController@timelist');

処理は、

    public function timelist(Request $request)
    {
        return response()->json(['timelist' => $this->registerManagement->getTimeList()]);
    }

    public function update(Request $request)
    {
        \Log::debug(print_r($request->contents, true));
        if($this->registerManagement->checkScheduleForUpdate($request->contents["start_day"], 
                                                            $request->contents["days"], 
                                                            $request->contents["id"], 
                                                            $request->contents["roomid"]) == false)
        {
            \Log::debug("スケジュールが重複しています");
            return response()->json([
                'errors' => "スケジュールが重複しています"
            ], 400);
        }
        $param = $this->registerManagement->getParam();
        $this->registerManagement->updateById($request->contents["id"],
        [
            $param[0] => $request->contents["name"],
            $param[1] => $request->contents["address"],
            $param[2] => $request->contents["phone"],
            $param[3] => $request->contents["num"],
            $param[4] => $request->contents["days"],
            $param[5] => $request->contents["start_day"],
            $param[6] => false,
            $param[7] => date('Y-m-d H:i', strtotime($request->contents["start_day"].' + '.$request->contents["days"].' day') + $request->contents["checkout"])
        ], $request->contents["roomid"]);
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room
        )]);
    }

timelist()というのは、データベースで処理するtick値と画面に表示する時刻のテーブルです。

これがあるためにつまずいた。

tick: “時刻(00:00)”

という形でデータを持っていたために、これがうまくVueで処理されないので、JavaScriptで形を変換したのですよ。

このやり方を見つけるのに苦労した・・・。

            getTimeList: function(){
                var self = this;
                axios.post('/api/timelist').then(function(response){
                    for (let [key, value] of Object.entries(response.data.timelist)){
                        self.timeList.push({key: key, value: value});
                    }
                }).catch(function(error){
                    console.log("失敗しました");
                });
            }

ここの処理ね、

[{tick: “時刻(00:00)”},{ },・・・]

このフォーマットを

[{key:tick, value:”時刻”},{ },・・・}

に変換したのですよ。

これを導き出すのに苦労しました。

でも、これができれば今までのサーバ側の処理が、ほぼ、そのまま使えるわけで、

編集画面でも、予約追加と同じことができるようになりました。

っていうか、追加処理もこの形にしよう。

ってなことで次回。

【Laravel】【Vue.js】【ホテル予約管理】予約詳細をダイアログに表示させる

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

この一覧の行をクリックすると、モーダルダイアログを出して、予約詳細を表示するように作り変えます。

必要な情報はすでに予約一覧で取得しているので、それをダイアログに表示させる、という手法で行きます。

        <div id="overlay" v-show="showContent" @click="closeModal">
            <div id="content">
                <table class="edit">
                    <tbody>
                        <tr>
                            <th>名前</th>
                            <td>{{ contents.name }}</td>
                        </tr>
                        <tr>
                            <th>住所</th>
                            <td>{{ contents.address }}</td>
                        </tr>
                        <tr>
                            <th>電話番号</th>
                            <td>{{ contents.phone }}</td>
                        </tr>
                        <tr>
                            <th>人数</th>
                            <td>{{ contents.num }}</td>
                        </tr>
                        <tr>
                            <th>宿泊部屋</th>
                            <td>{{ contents.room }}</td>
                        </tr>
                        <tr>
                            <th>宿泊日数</th>
                            <td>{{ contents.days }}</td>
                        </tr>
                        <tr>
                            <th>宿泊日</th>
                            <td>{{ contents.start_day }}</td>
                        </tr>
                        <tr>
                            <th>チェックアウト</th>
                            <td>{{ contents.checkout }}</td>
                        </tr>
                    </tbody>
                </table>
            <p><button @click="closeModal">close</button></p>
            </div>
        </div>
#overlay {
    z-index:1;
  
    position:fixed;
    top:0;
    left:0;
    width:100%;
    height:100%;
    background-color:rgba(0,0,0,0.5);
  
    display: flex;
    align-items: center;
    justify-content: center;
    
    #content{
        z-index:2;
        width:50%;
        padding: 1em;
        background:#fff;
    }
}

ダイアログとして表示する部分です。

overlayというのはダイアログのバックに表示するグレー表示のことです。

横と縦にでっかいdiv要素をtop0,left0に表示させる、というイメージですね。

その中にさらにdivを追加して詳細を表示を表示させます。

        data() {
            return {

        // 中略

                showContent: false,
                contents: {
                    name: "",
                    address: "",
                    phone: "",
                    num: 0,
                    room: "",
                    days: 0,
                    start_day: "",
                    checkout: "",
                }

contentsは実際に予約詳細のダイアログに表示するデータです。

showContentはoverlayのv-showとバインドさせて、ダイアログの表示・非表示を切り替えるフラグです。

            openModal: function(id){
                for(var i = 0; i< this.registers.length; i++){
                    if(this.registers[i].id == id){
                        this.contents.name = this.registers[i].name;
                        this.contents.address = this.registers[i].address;
                        this.contents.phone = this.registers[i].phone;
                        this.contents.num = this.registers[i].num;
                        this.contents.room = this.registers[i].room;
                        this.contents.days = this.registers[i].days;
                        this.contents.start_day = this.registers[i].start_day;
                        this.contents.checkout = this.registers[i].checkout;
                        break;
                    }
                }
                this.showContent = true
            },
            closeModal: function(){
                this.showContent = false
            }

ダイアログ表示処理とクローズ処理です。

やっていることは、基本的にデータを設定して、フラグをON、閉じるときはフラグをOFFにする、というだけです。

こんな感じに仕上がります。

次回はここから編集・削除を行いたいと思います。

【Laravel】【Vue.js】【ホテル予約管理】予約一覧を表示する

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、前回はフィルタの部分を作りましたが、今回は実際に予約一覧を取得して、表示させます。

まずは、予約一覧を取得するAPIを作ります。

ルーティング

Route::post('/api/registers', 'ApiController@registers');

コントローラー

    public function registers(Request $request)
    {
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room
        )]);
    }

コントローラで受け取るのは、前回作ったフィルタのパラメータをPOSTで受け取る、というやり方です。

そして、結果をJsonで返すという感じになります。

ではVueの実装です。

<template>
    <div>
        <table>
            <tbody>
                <tr>
                    <td>
                        <select v-model="selectYear" @click="getRegisters">
                            <option v-for="year in years" v-bind:value="year.value">{{ year.text }}</option>
                        </select>
                    </td>
                    <td>年</td>
                    <td>
                        <select v-model="selectMonth" @click="getRegisters">
                            <option v-for="month in months" v-bind:value="month.value">{{ month.text }}</option>
                        </select>
                    </td>
                    <td>月</td>
                        <select v-model="selectRoom" @click="getRegisters">
                            <option v-for="room in rooms" v-bind:value="room.id">{{ room.name }}</option>
                        </select>
                    </td>
                </tr>
            </tbody>
        </table>
        <table class="management">
            <tbody>
                <tr>
                    <th class="name">名前</th>
                    <th class="date">宿泊日</th>
                    <th class="checkout">チェックアウト</th>
                </tr>
                <tr v-for="register in registers">
                    <td class="name">{{ register.name }}</td>
                    <td class="date">{{ register.start_day }}</td>
                    <td class="checkout">{{ register.checkout }}</td>
                </tr>
            </tbody>
        </table>
    </div>
</template>

まず、フィルタの方は、コントロールを選択すると、予約一覧を取得する処理が走るようにしています。@click=”getRegisters”の部分です。

では、実際の処理。

<script>
    export default {
        data() {
            return {
                selectYear: 2019,
                years:[ 
                    {text:'2019',value:2019},
                    {text:'2020',value:2020},
                ],
                selectMonth: 1,
                months:[ 
                    {text:'1',value:1},
                    {text:'2',value:2},
                    {text:'3',value:3},
                    {text:'4',value:4},
                    {text:'5',value:5},
                    {text:'6',value:6},
                    {text:'7',value:7},
                    {text:'8',value:8},
                    {text:'9',value:9},
                    {text:'10',value:10},
                    {text:'11',value:11},
                    {text:'12',value:12},
                ],
                selectRoom: 0,
                rooms: [],
                result: [],
                param: {
                    year: 2019,
                    month: 1,
                    room: 1
                },
                registers: []
            }
        },
        created: function() {
            this.getRooms();
        },
        methods: {
            getRooms: function() {
                var self = this;
                axios.post('/api/rooms').then(function(response){
                    response.data.roomLists.forEach(element => {
                        self.rooms.push({id:element.id, name:element.name});
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            getRegisters: function() {
                var self = this;
                this.param.year = this.selectYear;
                this.param.month = this.selectMonth;
                this.param.room = this.selectRoom;
                axios.post('/api/registers', this.param).then(function(response){
                    self.registers = [];
                    response.data.registerLists.forEach(element => {
                        self.registers.push(
                            {
                                id:element.id, 
                                name:element.name, 
                                start_day:element.start_day, 
                                checkout:element.checkout
                            }
                        );
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            }
        }
    }
</script>

getRegisters()が呼ばれると、フィルタの条件を取得し、POSTパラメータに設定して、POSTリクエストを投げます。

その結果をregistersに設定して、その結果をテンプレート側のtrタグのv-forでリスト表示させていく、という動きになります。

リンクを作って編集・削除もやりたかっかけど、なんかうまく行かないので、Ajaxゴリゴリの方法に作り変えます。

【Laravel】【Vue.js】【ホテル予約管理】Vue.jsで実装する

最新ソースはこちら。

https://github.com/takishita2nd/hotel-mng

この画面を見て、思ったのは、

すげぇ窮屈だな、と。

この一覧画面に表示するのは、簡単な情報だけにして、それ以外の情報は、詳細画面へのリンクをクリックすることで表示させよう、と思ったのです。

やはり避けて通れない、Vue.jsの実装。

Laravelプログラマーの宿命。

まずは、部屋一覧を取得するWebAPIを実装します。

まずはルーティング。

Route::post('/api/rooms', 'ApiController@rooms');

コントローラー。

use Illuminate\Http\Request;
use App\Repository\RoomRepository;

class ApiController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
        $this->room = new RoomRepository();
    }

    public function rooms(Request $request)
    {
        return response()->json(['roomLists' => $this->room->getList()]);
    }
}

リポジトリは既存のものを使います。

違うのは、返信するデータはJsonであること。

Laravelでは一発でJsonに変換できるんですね。便利。

Viewの実装。

                    <detail-component></detail-component>

とりえあず、テンプレートの実態はVue.js側に書くので、ここではVueのテンプレートを配置するタグを書きます。

そして、Vueに追加したコンポーネントを有効化します。

Vue.component('detail-component', require('./components/DetailComponent.vue'));

resource/assets/js/app.jsに追加します。

こうすることで、npm run devで追加したコンポーネントもビルドに含めてくれます。

さぁ、ここからが本番だ。

まずはテンプレートの部分

<template>
    <div class="details">
        <table>
            <tr>
                <td>
                    <select v-model="selectYear">
                        <option v-for="year in years" v-bind:value="year.value">{{ year.text }}</option>
                    </select>
                </td>
                <td>年</td>
                <td>
                    <select v-model="selectMonth">
                        <option v-for="month in months" v-bind:value="month.value">{{ month.text }}</option>
                    </select>
                </td>
                <td>月</td>
                    <select v-model="selectRoom">
                        <option v-for="room in rooms" v-bind:value="room.id">{{ room.name }}</option>
                    </select>
                </td>
            </tr>
        </table>
    </div>
</template>

とりあえず、フィルタを行う年月と部屋を表示する実装にしています。

いずれ、予約一覧もこの中に入れます。

まだ途中なので。

<script>
    export default {
        data() {
            return {
                selectYear: 2019,
                years:[ 
                    {text:'2019',value:2019},
                    {text:'2020',value:2020},
                ],
                selectMonth: 1,
                months:[ 
                    {text:'1',value:1},
                    {text:'2',value:2},
                    {text:'3',value:3},
                    {text:'4',value:4},
                    {text:'5',value:5},
                    {text:'6',value:6},
                    {text:'7',value:7},
                    {text:'8',value:8},
                    {text:'9',value:9},
                    {text:'10',value:10},
                    {text:'11',value:11},
                    {text:'12',value:12},
                ],
                selectRoom: 0,
                rooms: [],
                result: [],
            }
        },
        created: function() {
            this.getRooms();
        },
        methods: {
            getRooms: function() {
                var self = this;
                axios.post('/api/rooms').then(function(response){
                    response.data.roomLists.forEach(element => {
                        console.log(element.name)
                        self.rooms.push({id:element.id, name:element.name});
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            }
        }
    }
</script>

年月はベタ書きです。

リストデータを定義して、v-forでoptionタグに展開する仕組み、v-modelで選択した値をバインドさせています。

この画面が読み込まれたら、axiosを使ってAjaxでデータの取得を行います。

axiosはデフォルトで組み込まれているみたい。Ajaxを行うためのライブラリです。

POSTで指定したURLに送信し、正常に受信できればthenの処理に、何かしらエラーが発生した場合はcatchの処理に移ります。

thisをselfに設定しているのは、thenやcatchの中ではthis=Vueではないので、selfにVueを退避させて置かないと、then/catchの中でVueのデータにアクセスできないためです。

で、thenの中で、レスポンスからデータを取得して、リスト化。

そのリストをテンプレート側で参照してoptionタグを作成します。

下のフィルタが今回Vueで表示させたものです。

上の部分はあとで消します。

【laravel】【ホテル予約管理】チェックアウト時間を追加する

最新ソースはこちら

https://github.com/takishita2nd/hotel-mng

さて、次の課題は、予約登録時にチェックアウト時間を登録し、日毎のチェックアウト時間と部屋を一覧に表示することです。

まずは、チェックアウト時間を登録する処理から作っていきます。

マイグレーションファイルを作成し、予約管理テーブルにチェックアウト時間を登録できるようにします。

class AddCheckout extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('reserve_managements', function (Blueprint $table) {
            $table->datetime('checkout')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('reserve_managements', function (Blueprint $table) {
            $table->dropColumn('checkout');
        });
    }
}

次は、予約追加画面にチェックアウト時間を登録できるようにするのですが、30分おきにリストから選択する、という形式にしましょうか。

    /**
     * 時間一覧を30分おきにして返す
     */
    public function getTimeList()
    {
        $time = 0;
        $ret = array();
        for($i = 0; $i < 48; $i++)
        {
            $time = strtotime('00:00 + '.($i * 30).' minute') - strtotime('00:00');
            $ret[$time] = date('H:i', $time);
        }
        return $ret;
    }

これでarray[‘チックタイム’] = ’時間:分’というような形でリストを作成します。

これを表示します。

    public function create()
    {
        return view('register.create',
                    [
                        'rooms' => $this->roomRepository->getRoomList(),
                        'timelist' => $this->registerManagement->getTimeList()
                    ]
                );
    }
                    <tr>
                        <th>チェックアウト</th>
                        <td>{!! Form::select('checkout', $timelist) !!}</td>
                    </tr>
    public function store(ManagementRequest $request)
    {
        if($this->registerManagement->checkSchedule($request->start_day, 
                                                    $request->days, 
                                                    $request->room) == false)
        {
            return redirect('management/create')
                        ->with(['error' => 'スケジュールが重複します'])
                        ->withInput();
        }
        $param = $this->registerManagement->getParam();
        $this->registerManagement->add([
            $param[0] => $request->name,
            $param[1] => $request->address,
            $param[2] => $request->phone,
            $param[3] => $request->num,
            $param[4] => $request->days,
            $param[5] => $request->start_day,
            $param[6] => false,
            $param[7] => date('Y-m-d H:i', strtotime($request->start_day.'+'.$request->days.' day') + $request->checkout)
        ], $request->room);
        return redirect('management');
    }

チェックアウト時間は、宿泊日+宿泊日数+チェックアウト時間で計算します。

で、これを一覧表示。

    public function getList()
    {
        $select = ['reserve_managements.id as id', 'reserve_managements.name as name', 'address', 'phone', 'num', 'rooms.name as room', 'checkout', 'start_day'];
        return ReserveManagement::select($select)
                                    ->where('lodging', false)
                                    ->orderBy('start_day')
                                    ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                    ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                    ->get();
    }
    public function getListByMonth($year, $month, $room)
    {
        $select = ['reserve_managements.id as id', 'reserve_managements.name as name', 'address', 'phone', 'num', 'rooms.name as room', 'checkout', 'start_day'];
        return ReserveManagement::select($select)
                                ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                ->where('start_day', '>=', date('Y-m-d', strtotime('first day of '.$year.'-'.$month)))
                                ->where('start_day', '<=', date('Y-m-d', strtotime('last day of '.$year.'-'.$month)))
                                ->where('reserve_management_room.room_id', $room)
                                ->where('lodging', false)
                                ->orderBy('start_day')
                                ->get();
    }

まぁ、やってることは、select句の中にcheckoutを追加しただけですが。

                    <table class="management">
                        <tr>
                            <th class="name">名前</th>
                            <th class="address">住所</th>
                            <th class="phone">電話番号</th>
                            <th class="num">人数</th>
                            <th class="room">部屋</th>
                            <th class="date">宿泊日</th>
                            <th class="checkout">チェックアウト</th>
                            <th class="command">編集</th>
                            <th class="command">削除</th>
                            <th class="command">宿泊</th>
                        </tr>
                    @foreach ($registerLists as $list)
                        <tr>
                            <td class="name">{{ $list->name }}</td>
                            <td class="address">{{ $list->address }}</td>
                            <td class="phone">{{ $list->phone }}</td>
                            <td class="num">{{ $list->num }}</td>
                            <td class="room">{{ $list->room }}</td>
                            <td class="date">{{ $list->start_day }}</td>
                            <td class="checkout">{{ $list->checkout }}</td>
                            <td class="command">{{ Html::link('/management/'.$list->id.'/edit', '編集') }}</td>
                            <td class="command">{{ Html::link('/management/'.$list->id.'/conform', '削除') }}</td>
                            <td class="command">
                            {!! Form::open(['url' => action('RegisterManagementController@lodging')]) !!}
                            {!! Form::hidden('id', $list->id) !!}
                            {!! Form::submit('宿泊') !!}
                            {!! Form::close() !!}
                            </td>
                        </tr>
                    @endforeach
                    </table>

宿泊日数のところをチェックアウト時間に置き換えました。

編集処理も同じように修正しています。

とりあえず、ここまで。