「Laravel」カテゴリーアーカイブ

【ダイエット支援】【食事管理】データ削除ダイアログ処理を作成する。

前回までの状況はこちら

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

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

データ削除処理を作成していきます。

まずはDeleteをクリックしたときの処理。

やり方はEditと同じです。

EatingDetailComponent.vue

        onClickDelete: function(timezone, id) {
            var deleteData = {};
            this.datalists[timezone].forEach(element => {
                if(element.id == id){
                    deleteData.id = id;
                    deleteData.date = this.date;
                    deleteData.item = element.item;
                    deleteData.timezone = timezone + 1;
                    deleteData.protein = element.protein;
                    deleteData.liqid = element.liqid;
                    deleteData.carbo = element.carbo;
                    deleteData.calorie = element.calorie;
                    return true;
                }
            });
            this.$refs.deleteDialog.dataSet(deleteData);
            this.showDeleteDialogContent = true;
        },

ダイアログ処理は体重管理のものを流用しています。

EatingDeleteDialogComponent.vue

<template>
    <div>
        <div id="overlay" v-show="show">
            <div id="content">
                <p v-if="error_flg == true" class="error">
                    <ui>
                        <li v-for="error in errors">{{ error }}</li>
                    </ui>
                </p>
                <table class="edit">
                    <tbody>
                        <tr>
                            <td>日付</td>
                            <td>{{contents.date}}</td>
                        </tr>
                        <tr>
                            <td>品名</td>
                            <td>{{contents.item}}</td>
                        </tr>
                        <tr>
                            <td>時間帯</td>
                            <td>{{contents.time}}</td>
                        </tr>
                        <tr>
                            <td>タンパク質</td>
                            <td>{{contents.protein}}</td>
                        </tr>
                        <tr>
                            <td>脂質</td>
                            <td>{{contents.liqid}}</td>
                        </tr>
                        <tr>
                            <td>炭水化物</td>
                            <td>{{contents.carbo}}</td>
                        </tr>
                        <tr>
                            <td>カロリー</td>
                            <td>{{contents.calorie}}</td>
                        </tr>
                    </tbody>
                </table>
                <p id="command">
                    <button @click="clickDelete">削除</button>
                    <button @click="closeModal">閉じる</button>
                </p>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    props: ['show'],
    data() {
        return {
            errors: [],
            error_flg: [],
            param: {},
            contents: {},
        };
    },
    created: function() {
    },
    methods: {
        dataSet: function(data) {
            this.contents = data;
            switch(this.contents.timezone) {
                case 1:
                    this.contents.time = "朝";
                    break;
                case 2:
                    this.contents.time = "昼";
                    break;
                case 3:
                    this.contents.time = "夜";
                    break;
                case 4:
                    this.contents.time = "間食";
                    break;
            }
        },
        clickDelete: function() {
            var self = this;
            this.param.contents = this.contents;
            axios.post('/api/eating/delete', this.param).then(function(response){
                self.closeModal();
                self.$emit('update');
            }).catch(function(error){
                self.error_flg = true;
                self.errors = error.response.data.errors;
            });
        },
        closeModal: function() {
            this.$parent.showDeleteDialogContent = false;
        },
    }
}
</script>

次はAPIの処理です。

これも体重管理の処理とほぼ同じ。

ApiController.php

    /**
     * データを一件削除する
     */
    public function delete(Request $request)
    {
        $this->eatingManagement->delete(Auth::user(),  $request->contents['id']);

        return response()->json();
    }
EatingManagementRepository.php

    /**
     * データを一件削除する
     */
    public function delete($user, $id)
    {
        $model = $user->EatingManagements()->where('id', $id)->first();
        $timezone = $model->timezones()->first();

        $this->detachToUser($model, $user);
        $this->detachToTimezone($model, $timezone);
        $model->delete();
    }

違いはtimezoneテーブルとのリンクを削除する処理が増えたぐらいで、やっていることはそんなに難しくないです。

すんなり完成しました。

これでCURD処理一通り完成したので、次回はグラフ処理を作成していこうと思います。

【ダイエット支援】【食事管理】データ編集ダイアログ処理を作成する。

前回までの状況はこちら。

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

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

データ編集処理を作成していきます。

やり方は、体重管理機能のものと同じです。

EatingEditDialogComponent.vue

<template>
    <div>
        <div id="overlay" v-show="show">
            <div id="content">
                <p v-if="error_flg == true" class="error">
                    <ui>
                        <li v-for="error in errors">{{ error }}</li>
                    </ui>
                </p>
                <table class="edit">
                    <tbody>
                        <tr>
                            <td>日付</td>
                            <td>
                                <input type="date" v-model="contents.date" v-if="datehold" readonly>
                                <input type="date" v-model="contents.date" v-else>
                            </td>
                        </tr>
                        <tr>
                            <td>品名</td>
                            <td><input type="text" v-model="contents.item" /></td>
                        </tr>
                        <tr>
                            <td>時間帯</td>
                            <td>
                                <select name="timezone" v-model="contents.timezone">
                                    <option value="1" selected>朝</option>
                                    <option value="2">昼</option>
                                    <option value="3">夜</option>
                                    <option value="4">間食</option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <td>タンパク質</td>
                            <td><input type="number" v-model="contents.protein" /></td>
                        </tr>
                        <tr>
                            <td>脂質</td>
                            <td><input type="number" v-model="contents.liqid" /></td>
                        </tr>
                        <tr>
                            <td>炭水化物</td>
                            <td><input type="number" v-model="contents.carbo" /></td>
                        </tr>
                        <tr>
                            <td>カロリー</td>
                            <td><input type="number" v-model="contents.calorie" /></td>
                        </tr>
                    </tbody>
                </table>
                <p id="command">
                    <button @click="clickEdit">編集</button>
                    <button @click="closeModal">閉じる</button>
                </p>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    props: ['show', 'date', 'datehold'],
    data() {
        return {
            errors: [],
            error_flg: [],
            param: {},
            contents: {},
        };
    },
    methods: {
        dataSet: function(data) {
            this.contents = data;
        },
        clickEdit: function() {
            var self = this;
            this.param.contents = this.contents;
            axios.post('/api/eating/update', this.param).then(function(response){
                self.clear();
                self.closeModal();
                self.$emit('update');
            }).catch(function(error){
                self.error_flg = true;
                self.errors = error.response.data.errors;
            });
        },
        closeModal: function() {
            this.$parent.showEditDialogContent = false;
        },
        clear: function() {
            this.contents.date = this.date;
            this.contents.item = "";
            this.contents.timezone = 1;
            this.contents.protein = "";
            this.contents.liqid = "";
            this.contents.carbo = "";
            this.contents.calorie = "";
            this.error_flg = false;
            this.errors = [];
        }
    }
}
</script>
EatingDetailComponent.vue

        <eating-edit-dialog-component ref="editDialog" :show="showEditDialogContent" :date="date" :datehold=true @update="invokeUpdateList"></eating-edit-dialog-component>

        onClickEdit: function(timezone, id) {
            var editData = {};
            this.datalists[timezone].forEach(element => {
                if(element.id == id){
                    editData.id = id;
                    editData.date = this.date;
                    editData.item = element.item;
                    editData.timezone = timezone + 1;
                    editData.protein = element.protein;
                    editData.liqid = element.liqid;
                    editData.carbo = element.carbo;
                    editData.calorie = element.calorie;
                    return true;
                }
            });
            this.$refs.editDialog.dataSet(editData);
            this.showEditDialogContent = true;
        },

Editボタンをクリックすると、detail側でEditダイアログに表示するパラメータを作成し、Editダイアログに渡します。

refパラメータを使用することで、親から子のモジュールの関数を呼び出すことができるので、これを利用してパラメータ一式を子モジュールに渡します。

編集ダイアログで編集をクリックすると、APIを呼び出してデータの更新を行います。

ApiController.php

    /**
     * データを一件取得する
     */
    public function update(Request $request)
    {
        $paramNames = $this->eatingManagement->getParam();

        $param = [];
        foreach($paramNames as $name) {
            $param[$name] = $request->contents[$name];
        }

        $this->eatingManagement->update($param, Auth::user(),  $request->contents['id'], $request->contents['timezone']);
        
        return response()->json();
    }
EatingManagementRepository.php

    /**
     * データを一件取得する
     */
    public function update($param, $user, $id, $timezone)
    {
        $model = $user->EatingManagements()->where('id', $id)->first();
        foreach($this->paramNames as $name)
        {
            $model->$name = $param[$name];
        }
        $model->save();
        $oldtime = $model->timezones()->first();
        $newtime = Timezone::where('id', $timezone)->first();

        $this->detachToTimezone($model, $oldtime);
        $this->attachToTimezone($model, $newtime);
    }

データを更新するときは、IDからデータベースのデータを取得し、これのデータを上書きすることで更新できます。

ただ、時間帯の更新というのもありえるので、timezonesテーブルとのリンクを再構築しなければなりません。

いったん、リレーションからtimezonesとデタッチし、新しいtimezonesとアタッチします。

これが終わったら、入力と同様にダイアログを非表示にし、detail画面を更新します。

ちょっと表示に手間取ったけど、できました。

【ダイエット支援】【食事管理】データ詳細から入力処理を作成する。

前回までの状況はこちら。

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

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

データ入力処理ですが、すでにデータ入力画面や一連の処理は作成してあるので、同じ要領でデータ詳細画面にもデータ入力画面への導線を作成します。

EatingDetailComponent.vue

<template>

<-- 中略 -->

            <p id="inputbutton">
                <button @click="onClickInput">データ入力</button>
            </p>

<-- 中略 -->

        <eating-input-dialog-component :show="showInputDialogContent" :date="date" :datehold=false @update="invokeUpdateList"></eating-input-dialog-component>

<-- 中略 -->

</template>

<script>
export default {

    //中略

    methods: {
        onClickInput: function() {
            this.showInputDialogContent = true;
        },
        invokeUpdateList: function() {
            this.updateList();
        },

【ダイエット支援】【食事管理】データ詳細処理を作成する。

前回までの状況はこちら。

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

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

今回はこの画面を作成していきます。

前回作成した画面の、

Editをクリックしたときの、遷移先の画面です。

まずは、画面遷移のところを作成する。

EatingListComponent.vue

                        <td class="edit"><a @click="onClickEdit(data.date)">Edit</a></td>


export default {
    data() {
        return {
            url: "eating/detail"
        };
    },
    methods: {
        onClickEdit: function(date) {
            window.location = this.url + "/" + date;
        },

Editをクリックすると、onClickEdit()が実行され、window.locationにURLを設定することで、そのURLに遷移します。

で、遷移先の画面。

web.php

Route::get('/eating/detail/{date}', 'Eating\EatingController@detail')->name('eating/detail');
EatingController.php

class EatingController extends Controller
{
    public function detail($date)
    {
        return view('eatingdetail', ['date' => $date]);
    }
eatingdetail.blade.php

@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">
                    @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                    @endif
                </div>
                <eating-detail-component date={{$date}}></eating-detail-component>
            </div>
        </div>
    </div>
</div>
@endsection

詳細画面のURLはeating/detail/(日付)となります。

この(日付)の部分が巡り巡って、eating-detail-componentに渡ります。

<eating-detail-component date={{$date}}></eating-detail-component>

ここで一度詰まった。

:date={{$date}}と書いてはダメみたい。

で、表示するデータの取得。

class EatingManagementRepository
{
    private $paramNames = ['date', 'item', 'protein', 'liqid', 'carbo', 'calorie'];

    public function getDetails($user, $date)
    {
        $eatings = $user->EatingManagements()
            ->where(DB::raw('date_format(date, "%Y-%m-%d")'), $date)
            ->get();
        
        $retDatas = [];
        $index = [0, 0, 0, 0];
        foreach($eatings as $eating) {
            $timezone = $eating->timezones()->first();
            for($j = 1; $j < count($this->paramNames); $j++) {
                $retDatas[$timezone->id - 1][$index[$timezone->id - 1]][$this->paramNames[$j]] = $eating->{$this->paramNames[$j]};
            }
            $index[$timezone->id - 1]++;
        }
        return $retDatas;
    }

今回もかなりカオスな処理。

時間帯によってデータを分けたかったので、

retData[時間帯番号][index][詳細データ]

という形にデータを作成しています。

詳細データの連想配列の名前は、定義済みの$paramNamesの値を使用します。

これをAPIで呼び出します。

class ApiController extends Controller
{
    /**
     * 一日分のデータを取得する
     */
    public function detail(Request $request)
    {
        return response()->json(['dataLists' => $this->eatingManagement->getDetails(Auth::user(), $request->contents['date'])]);
    }
web.php

Route::post('api/eating/detail', 'Eating\ApiController@detail');
<template>
    <div>
        <div>
            <p id="navi">> <a href="/home">HOME</a> / <a href="/eating">食事管理</a></p>
            <p>{{date}}</p>
            <table class="eatingdetail">
                <caption>朝</caption>
                <tbody>
                    <tr>
                        <th class="item">品名</th>
                        <th class="protein">タンパク質</th>
                        <th class="liqid">脂質</th>
                        <th class="carbo">炭水化物</th>
                        <th class="calorie">カロリー</th>
                        <th class="edit"></th>
                    </tr>
                    <tr v-for="data in datalists[0]">
                        <td class="item">{{ data.item}}</td>
                        <td class="protein">{{ data.protein}}</td>
                        <td class="liqid">{{ data.liqid}}</td>
                        <td class="carbo">{{ data.carbo}}</td>
                        <td class="calorie">{{ data.calorie}}</td>
                        <td class="edit"><a @click="onClickEdit(data.date)">Edit</a></td>
                    </tr>
                </tbody>
            </table>
            <table class="eatingdetail">
                <caption>昼</caption>
                <tbody>
                    <tr>
                        <th class="item">品名</th>
                        <th class="protein">タンパク質</th>
                        <th class="liqid">脂質</th>
                        <th class="carbo">炭水化物</th>
                        <th class="calorie">カロリー</th>
                        <th class="edit"></th>
                    </tr>
                    <tr v-for="data in datalists[1]">
                        <td class="item">{{ data.item}}</td>
                        <td class="protein">{{ data.protein}}</td>
                        <td class="liqid">{{ data.liqid}}</td>
                        <td class="carbo">{{ data.carbo}}</td>
                        <td class="calorie">{{ data.calorie}}</td>
                        <td class="edit"><a @click="onClickEdit(data.date)">Edit</a></td>
                    </tr>
                </tbody>
            </table>
            <table class="eatingdetail">
                <caption>夜</caption>
                <tbody>
                    <tr>
                        <th class="item">品名</th>
                        <th class="protein">タンパク質</th>
                        <th class="liqid">脂質</th>
                        <th class="carbo">炭水化物</th>
                        <th class="calorie">カロリー</th>
                        <th class="edit"></th>
                    </tr>
                    <tr v-for="data in datalists[2]">
                        <td class="item">{{ data.item}}</td>
                        <td class="protein">{{ data.protein}}</td>
                        <td class="liqid">{{ data.liqid}}</td>
                        <td class="carbo">{{ data.carbo}}</td>
                        <td class="calorie">{{ data.calorie}}</td>
                        <td class="edit"><a @click="onClickEdit(data.date)">Edit</a></td>
                    </tr>
                </tbody>
            </table>
            <table class="eatingdetail">
                <caption>間食</caption>
                <tbody>
                    <tr>
                        <th class="item">品名</th>
                        <th class="protein">タンパク質</th>
                        <th class="liqid">脂質</th>
                        <th class="carbo">炭水化物</th>
                        <th class="calorie">カロリー</th>
                        <th class="edit"></th>
                    </tr>
                    <tr v-for="data in datalists[3]">
                        <td class="item">{{ data.item}}</td>
                        <td class="protein">{{ data.protein}}</td>
                        <td class="liqid">{{ data.liqid}}</td>
                        <td class="carbo">{{ data.carbo}}</td>
                        <td class="calorie">{{ data.calorie}}</td>
                        <td class="edit"><a @click="onClickEdit(data.date)">Edit</a></td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script>
export default {
    props: {
        date: String
    },
    data() {
        return {
            showInputDialogContent: false,
            showEditDialogContent: false,
            showDeleteDialogContent: false,
            datalists: [],
            param: {},
            contents: {
                date: "",
            },
        };
    },
    created: function() {
        this.updateList();
    },
    methods: {
        onClickEdit: function(date) {
        },
        invokeUpdateList: function() {
        },
        updateList: function() {
            this.datalists = [];
            this.contents.date = this.date;
            this.param.contents = this.contents;
            var self = this;
            axios.post('/api/eating/detail', this.param).then(function(response){
                response.data.dataLists.forEach(element => {
                    var data = [];
                    element.forEach(element2 => {
                        data.push({
                            item: element2.item,
                            protein: element2.protein,
                            liqid: element2.liqid,
                            carbo: element2.carbo,
                            calorie: element2.calorie
                        })
                    })
                    self.datalists.push(data);
                });
            }).catch(function(error){
            });
        }
    }
}
</script>

responseの処理がネストになるという、またまたカオスな処理。

でもこれでJS側でもdatalists[時間帯ID][index].詳細データという形になります。

これをテンプレートに表示させれば良い。

【ダイエット支援】【食事管理】データ一覧処理を作成する。

前回までの状況はこちら。

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

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

今回はデータ一覧画面を作成していきます。

この画面は日付ごとにタンパク質、脂質、炭水化物、カロリーを集計して一覧表示にします。

今回の一番の肝、データを処理するリポジトリの処理。

class EatingManagementRepository
{
    private $paramNames = ['date', 'item', 'protein', 'liqid', 'carbo', 'calorie'];

    /**
     * データを取得して日毎にまとめる
     */
    public function getDailyList($user, $page = 1, $days = 10)
    {
        $dates = [];
        for($i = ($page - 1); $i < ($days * $page) ; $i++) {
            $dates[] = date('Y-m-d', strtotime('today - '.$i.' day'));
        }

        $eatings = $user->EatingManagements()
             ->whereIn(DB::raw('date_format(date, "%Y-%m-%d")'), $dates)
             ->get();

        // 日毎に集計
        $dailyDatas = [];
        foreach($eatings as $eating) {
            for($j = 2; $j < count($this->paramNames); $j++) {
                if(!array_key_exists($eating->date, $dailyDatas)) {
                    $dailyDatas[$eating->date] = [];
                }
                if(!array_key_exists($this->paramNames[$j], $dailyDatas[$eating->date])) {
                    $dailyDatas[$eating->date][$this->paramNames[$j]] = 0;
                }
                $dailyDatas[$eating->date][$this->paramNames[$j]] += $eating->{$this->paramNames[$j]};
            }
        }

        // 戻り値に変換
        $retDatas = [];
        $index = 0;
        foreach($dailyDatas as $dailykey => $dailyData) {
            $retDatas[$index][$this->paramNames[0]] = $dailykey;
            for($k = 2; $k < count($this->paramNames); $k++) {
                $retDatas[$index][$this->paramNames[$k]] = $dailyDatas[$dailykey][$this->paramNames[$k]];
            }
        }

        return $retDatas;
    }

まずはwhere inで日付指定でデータを取得するために、今日から(デフォルト)10日前までの日付を配列$datesに作成します。

>>> $dates
=> [
     "2020-07-30",
     "2020-07-29",
     "2020-07-28",
     "2020-07-27",
     "2020-07-26",
     "2020-07-25",
     "2020-07-24",
     "2020-07-23",
     "2020-07-22",
     "2020-07-21",
   ]

そして、DBにアクセスして、データを取得、結果が$eatingsに入ります。

>>> $eatings = $user->EatingManagements()->whereIn(DB::raw('date_format(date, "%Y-%m-%d")'), $dates)->get();=> Illuminate\Database\Eloquent\Collection {#3920     all: [
       App\Model\EatingManagement {#3917
         id: 1,
         date: "2020-07-30",
         item: "item1",
         protein: 10,
         liqid: 10,
         carbo: 10,
         calorie: 10,
         created_at: "2020-07-30 10:38:50",
         updated_at: "2020-07-30 10:38:50",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3916
           user_id: 1,
           eating_management_id: 1,
         },
       },
       App\Model\EatingManagement {#3652
         id: 2,
         date: "2020-07-30",
         item: "item2",
         protein: 20,
         liqid: 20,
         carbo: 20,
         calorie: 20,
         created_at: "2020-07-30 10:39:00",
         updated_at: "2020-07-30 10:39:00",
         pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3915
           user_id: 1,
           eating_management_id: 2,
         },
       },
     ],
   }

このデータを日付毎に集計します。

$dailyDatasは二次元の連想配列になっていて、$dailyDatas[日付][栄養素]という感じで格納されます。

栄養素の連想配列名は定義済みの$paramNamesの値をそのまま使用します。

こうすることで、今後何かDBに修正が入ったとしても、$paramNamesのみを修正すれば良いことになります。

>>> $dailyDatas
=> [
     "2020-07-30" => [
       "protein" => 30,
       "riqid" => 0,
       "carbo" => 30,
       "calorie" => 30,
     ],
   ]

ただ、このままだと、Json化してJavascript側で処理するのに、ものすごい面倒なことになるので、扱いやすいようにデータを変換します。

具体的には、$retDatas[データ番号][項目]という形にします。

>>> $retDatas
=> [
     [
       "date" => "2020-07-30",
       "protein" => 30,
       "riqid" => 0,
       "carbo" => 30,
       "calorie" => 30,
     ],
   ]

この状態でJson化してフロントエンド側に送信します。

この処理書くの大変だったわ。

namespace App\Http\Controllers\Eating;

class ApiController extends Controller
{
    /**
     * データ一覧を取得する
     */
    public function list(Request $request)
    {
        return response()->json(['dataLists' => $this->eatingManagement->getDailyList(Auth::user(), $request->contents["page"])]);
    }
Route::post('api/eating/list', 'Eating\ApiController@list');
<script>
export default {
    created: function() {
        this.updateList();
        //this.createPagenate();
    },
    methods: {
        updateList: function() {
            this.datalists = [];
            this.contents.page = this.currentPage;
            this.param.contents = this.contents;
            var self = this;
            axios.post('api/eating/list', this.param).then(function(response){
                response.data.dataLists.forEach(element => {
                    self.datalists.push({
                        date: element.date,
                        protein: element.protein,
                        liqid: element.liqid,
                        carbo: element.carbo,
                        calorie: element.calorie
                    })
                });
            }).catch(function(error){
            });
        }

いやー未だにPHP慣れないわー。

【ダイエット支援】【食事管理】データ入力処理を作成する。

前回までの状況はこちら。

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

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

データ入力ダイアログから入力データをデータベースに登録するところまで行きます。

モデル

class User extends Authenticatable
{
    public function EatingManagements()
    {
        return $this->belongsToMany('App\Model\EatingManagement');
    }
class EatingManagement extends Model
{
    protected $table = 'eating_managements';
    
    public function users()
    {
        return $this->belongsToMany('App\User');
    }

    public function timezones()
    {
        return $this->belongsToMany('App\Model\Timezone');
    }
}
class Timezone extends Model
{
    protected $table = 'timezones';
    
    public function Eating()
    {
        return $this->belongsToMany('App\Model\EatingManagement');
    }
}

リポジトリ

<?php

namespace App\Repository;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Model\EatingManagement;
use App\Model\Timezone;
use App\User;

class EatingManagementRepository
{
    private $paramNames = ['date', 'item', 'protein', 'riqid', 'carbo', 'calorie'];

    public function __construct()
    {

    }

    public function add($param, $user, $timezone)
    {
        $model = new EatingManagement();
        foreach($this->paramNames as $name)
        {
            $model->$name = $param[$name];
        }
        $model->save();
        $time = Timezone::where('id', $timezone)->first();

        $this->attachToUser($model, $user);
        $this->attachToTimezone($model, $time);
    }

    public function attachToUser($model, $user)
    {
        $model->users()->attach($user);
    }

    public function detachToUser($model, $user)
    {
        $model->users()->detach($user);
    }

    public function attachToTimezone($model, $timezone)
    {
        $model->timezones()->attach($timezone);
    }

    public function detachToTimezone($model, $timezone)
    {
        $model->timezones()->detach($timezone);
    }

    public function getParam()
    {
        return $this->paramNames;
    }

}

コントローラー

<?php

namespace App\Http\Controllers\Eating;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\Repository\EatingManagementRepository;

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

    /**
     * データを1件登録する
     */
    public function add(Request $request)
    {
        $paramNames = $this->eatingManagement->getParam();

        $param = [];
        foreach($paramNames as $name) {
            $param[$name] = $request->contents[$name];
        }

        $this->eatingManagement->add($param, Auth::user(), $request->contents['timezone']);
        
        return response()->json();
    }

}

パラメータを設定する処理をちょっと変えています。

Vue側でtypoしていないことが前提ですが、なるべくコードの中で直値を使わない(定義する箇所は一か所のみ)ようにするための工夫。

これが正解なのか、いまだにわかりませんけどね。

Vue側の修正です。

EatingDashboardComponent.vue

<template>
    <div>
        <div class="dashboard">
            <div class="chart">
                <canvas id="eating"></canvas>
            </div>
            <div class="command">
                <ul>
                    <li><a @click="onClickPrev">prev</a></li>
                    <li><a @click="onClickNext">next</a></li>
                </ul>
                <ul>
                    <li><a @click="onClickInput">クイック入力</a></li>
                    <li><a href="/eating">詳細</a></li>
                </ul>
            </div>
        </div>
        <eating-input-dialog-component :show="showInputDialogContent" :datehold=true @update="invokeUpdateList"></eating-input-dialog-component>
    </div>
</template>

ダッシュボードからのクイック入力からは、日付の入力はできないようにします。(本日固定にする。)

そのために、コンポーネントにdateholdというパラメータを追加で渡しています。

EatingInputDialogComponent.vue

<template>
    <div>
        <div id="overlay" v-show="show">
            <div id="content">
                <p v-if="error_flg == true" class="error">
                    <ui>
                        <li v-for="error in errors">{{ error }}</li>
                    </ui>
                </p>
                <table class="edit">
                    <tbody>
                        <tr>
                            <td>日付</td>
                            <td>
                                <input type="date" v-model="contents.date" v-if="datehold" readonly>
                                <input type="date" v-model="contents.date" v-else>
                            </td>
                        </tr>
                        <tr>
                            <td>品名</td>
                            <td><input type="text" v-model="contents.item" /></td>
                        </tr>
                        <tr>
                            <td>時間帯</td>
                            <td>
                                <select name="timezone" v-model="contents.timezone">
                                    <option value="1" selected>朝</option>
                                    <option value="2">昼</option>
                                    <option value="3">夜</option>
                                    <option value="4">間食</option>
                                </select>
                            </td>
                        </tr>
                        <tr>
                            <td>タンパク質</td>
                            <td><input type="number" v-model="contents.protein" /></td>
                        </tr>
                        <tr>
                            <td>脂質</td>
                            <td><input type="number" v-model="contents.riqid" /></td>
                        </tr>
                        <tr>
                            <td>炭水化物</td>
                            <td><input type="number" v-model="contents.carbo" /></td>
                        </tr>
                        <tr>
                            <td>カロリー</td>
                            <td><input type="number" v-model="contents.calorie" /></td>
                        </tr>
                    </tbody>
                </table>
                <p id="command">
                    <button @click="clickAdd">入力</button>
                    <button @click="closeModal">閉じる</button>
                </p>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    props: ['show', 'datehold'],
    data() {
        return {
            errors: [],
            error_flg: [],
            param: {},
            contents: {
                date: "",
                item: "",
                timezone: 1,
                protein: "",
                riqid: "",
                carbo: "",
                calorie: "",
            },
        };
    },
    created: function() {
        this.clear();
    },
    methods: {
        clickAdd: function() {
            var self = this;
            this.param.contents = this.contents;
            axios.post('api/eating/add', this.param).then(function(response){
                self.clear();
                self.closeModal();
                self.$emit('update');
            }).catch(function(error){
                self.error_flg = true;
                self.errors = error.response.data.errors;
            });
        },
        closeModal: function() {
            this.$parent.showInputDialogContent = false;
        },
        clear: function() {
            var today = new Date();
            this.contents.date = today.getFullYear() + "-" + ('00'+(today.getMonth() + 1)).slice( -2 ) + "-" + today.getDate();
            this.contents.item = "";
            this.contents.timezone = 1;
            this.contents.protein = "";
            this.contents.riqid = "";
            this.contents.carbo = "";
            this.contents.calorie = "";
            this.error_flg = false;
            this.errors = [];
        }
    }
}
</script>

ちょっと苦労したのはdateのフォーマットですね。

dateフォームのフォーマットは”YYYY-MM-DD”で、これが若干違っていてもフォームは正しく認識してくれません。

実行結果。

【ダイエット支援】【食事管理】詳細画面を作成する。

前回までの状況はこちら。

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

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

詳細画面を作成していきます。

これも体重管理の画面を流用してさくっと作成していきます。

処理部分は後で実装します。

web.php

Route::get('/eating', 'Eating\EatingController@index')->name('eating');
EatingController.php

<?php

namespace App\Http\Controllers\Eating;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class EatingController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('eating');
    }
}
eating.blade.php

@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">
                    @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                    @endif
                </div>
                <eating-list-component></eating-list-component>
            </div>
        </div>
    </div>
</div>
@endsection
app.js

Vue.component('eating-list-component', require('./components/Eating/EatingListComponent.vue'));
EatingDashboardComponent.vue

<template>
    <div>
        <div class="dashboard">
            <div class="chart">
                <canvas id="eating"></canvas>
            </div>
            <div class="command">
                <ul>
                    <li><a @click="onClickPrev">prev</a></li>
                    <li><a @click="onClickNext">next</a></li>
                </ul>
                <ul>
                    <li><a @click="onClickInput">クイック入力</a></li>
                    <li><a href="/eating">詳細</a></li>
                </ul>
            </div>
        </div>
        <eating-input-dialog-component :show="showInputDialogContent" @update="invokeUpdateList"></eating-input-dialog-component>
    </div>
</template>
EatingListComponent.vue

<template>
    <div>
        <div>
            <p id="navi">> <a href="/home">HOME</a></p>
            <p id="inputbutton">
                <button @click="onClickInput">データ入力</button>
            </p>
            <div id="pagenate">
                <ul>
                    <li>
                        <a href="#" v-if="prevShow" @click="prevPage()">&lt;</a>
                        <b v-else>&lt;</b>
                    </li>
                    <li v-for="page in pagenates">
                        <a href="#" v-if="currentPage != page" @click="changePage(page)">{{ page }}</a>
                        <b v-else>{{ page }}</b>
                    </li>
                    <li>
                        <a href="#" v-if="nextShow" @click="nextPage()">&gt;</a>
                        <b v-else>&gt;</b>
                    </li>
                </ul>
            </div>
            <table class="eatinglist">
                <tbody>
                    <tr>
                        <th class="date">日時</th>
                        <th class="protein">タンパク質</th>
                        <th class="liqid">脂質</th>
                        <th class="carbo">炭水化物</th>
                        <th class="calorie">カロリー</th>
                        <th class="edit"></th>
                        <th class="delele"></th>
                    </tr>
                    <tr v-for="data in datalists">
                        <td class="date">{{ data.date}}</td>
                        <td class="protein">{{ data.protein}}</td>
                        <td class="liqid">{{ data.liqid}}</td>
                        <td class="carbo">{{ data.carbo}}</td>
                        <td class="calorie">{{ data.calorie}}</td>
                        <td class="edit"><a @click="onClickEdit(data.id)">Edit</a></td>
                        <td class="delele"><a @click="onClickDelete(data.id)">Delete</a></td>
                    </tr>
                </tbody>
            </table>
        </div>
        <div>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            showInputDialogContent: false,
            showEditDialogContent: false,
            showDeleteDialogContent: false,
            datalists: [],
            pagenates: [],
            currentPage: 1,
            maxPage: 1,
            param: {},
            contents: {
                page: "",
            },
        };
    },
    computed: {
        prevShow: function() {
            return this.currentPage != 1;
        },
        nextShow: function() {
            return this.currentPage != this.maxPage;
        },
    },
    created: function() {
    },
    methods: {
        createPagenate: function() {
        },
        changePage: function(page) {
        },
        nextPage: function() {
        },
        prevPage: function() {
        },
        onClickInput: function() {
        },
        onClickEdit: function(id) {
        },
        onClickDelete: function(id) {
        },
        invokeUpdateList: function() {
        },
        updateList: function() {
        }
    }
}
</script>
app.scss

@import "eatinglist";
_eatinglist.scss

table.eatinglist {
    width: 100%;
    tbody tr {
        th {
            border: solid;
            border-width: thin;
        }
        .date {
            width: 20%;
        }
        .protein {
            width: 15%;
        }
        .liqid {
            width: 15%;
        }
        .carbo {
            width: 15%;
        }
        .calorie {
            width: 15%;
        }
        .edit {
            width: 10%;
        }
        .delete {
            width: 10%;
        }
        td {
            border: solid;
            border-width: thin;
            text-align: right;
        }
        .date {
            width: 20%;
        }
        .protein {
            width: 15%;
        }
        .liqid {
            width: 15%;
        }
        .carbo {
            width: 15%;
        }
        .calorie {
            width: 15%;
        }
        .edit {
            width: 10%;
        }
        .delete {
            width: 10%;
        }
    }
}
p#inputbutton {
    text-align: right;
    margin-right: 10px;
}
p#navi {
    margin-left: 10px;
}
div#pagenate {
    text-align: right;
    ul li {
        margin-left: 5px;
        margin-right: 5px;
        display: inline;
    }
}

まぁ、まだデータがないからね。

【Laravel】【ダイエット支援】【食事管理】入力ダイアログを作成する

前回までの状況はこちら。

最新ソースファイルはこちら(gitHub)

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

入力ダイアログの画面を作成していきます。

ベースは体重管理で作成したものを使用します。

実際の処理は後で作成します。

EatingInputDialogComponent.vue

<template>
    <div>
        <div id="overlay" v-show="show">
            <div id="content">
                <p v-if="error_flg == true" class="error">
                    <ui>
                        <li v-for="error in errors">{{ error }}</li>
                    </ui>
                </p>
                <table class="edit">
                    <tbody>
                        <tr>
                            <td>品名</td>
                            <td><input type="text" v-model="contents.item" /></td>
                        </tr>
                        <tr>
                            <td>タンパク質</td>
                            <td><input type="number" v-model="contents.protein" /></td>
                        </tr>
                        <tr>
                            <td>脂質</td>
                            <td><input type="number" v-model="contents.riqid" /></td>
                        </tr>
                        <tr>
                            <td>炭水化物</td>
                            <td><input type="number" v-model="contents.carbo" /></td>
                        </tr>
                        <tr>
                            <td>カロリー</td>
                            <td><input type="number" v-model="contents.calorie" /></td>
                        </tr>
                    </tbody>
                </table>
                <p id="command">
                    <button @click="clickAdd">入力</button>
                    <button @click="closeModal">閉じる</button>
                </p>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    props: ['show'],
    data() {
        return {
            errors: [],
            error_flg: [],
            param: {},
            contents: {
                item: "",
                protein: "",
                riqid: "",
                carbo: "",
                calorie: "",
            },
        };
    },
    created: function() {
    },
    methods: {
        clickAdd: function() {
        },
        closeModal: function() {
            this.$parent.showInputDialogContent = false;
        },
        clear: function() {
            this.contents.item = "";
            this.contents.protein = "";
            this.contents.riqid = "";
            this.contents.carbo = "";
            this.contents.calorie = "";
            this.error_flg = false;
            this.errors = [];
        }
    }
}
</script>
app.js

Vue.component('eating-input-dialog-component', require('./components/Eating/EatingInputDialogComponent.vue'));
EatingDashboardComponent.vue

<template>
    <div>
        <div class="dashboard">
            <div class="chart">
                <canvas id="eating"></canvas>
            </div>
            <div class="command">
                <ul>
                    <li><a @click="onClickPrev">prev</a></li>
                    <li><a @click="onClickNext">next</a></li>
                </ul>
                <ul>
                    <li><a @click="onClickInput">クイック入力</a></li>
                    <li><a href="">詳細</a></li>
                </ul>
            </div>
        </div>
        <eating-input-dialog-component :show="showInputDialogContent" @update="invokeUpdateList"></eating-input-dialog-component>
    </div>
</template>

<script>
export default {
    data() {
        return {
            showInputDialogContent: false,
        };
    },
    methods: {
        onClickInput: function() {
            this.showInputDialogContent = true;
        },
        invokeUpdateList: function() {
            //this.graphUpdate(this.graphNum);
        },
    }
}
</script>

【LARAVEL】【ダイエット支援】ダッシュボードに食事管理を追加する

前回までの状況はこちら

最新ソースファイルはこちら(gitHub)

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

ダッシュボード画面に、

この画面の追加します。

ベースは体重管理のものを流用できるので、さくっと作成しました。

EatingDashboardComponent.vue

<template>
    <div>
        <div class="dashboard">
            <div class="chart">
                <canvas id="eating"></canvas>
            </div>
            <div class="command">
                <ul>
                    <li><a @click="onClickPrev">prev</a></li>
                    <li><a @click="onClickNext">next</a></li>
                </ul>
                <ul>
                    <li><a @click="onClickInput">クイック入力</a></li>
                    <li><a href="">詳細</a></li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
        };
    },
    created: function() {
    },
    mounted: function() {
    },
    methods: {
        onClickNext: function() {
        },
        onClickPrev: function() {
        },
        onClickInput: function() {
        },
    }
}
</script>
app.js

Vue.component('eating-dashboard-component', require('./components/Eating/EatingDashboardComponent.vue'));
home.blade.php

@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">Dashboard</div>

                <div class="panel-body">
                    @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                    @endif

                    <weight-dashboard-component></weight-dashboard-component>
                    <p></p>
                    <eating-dashboard-component></eating-dashboard-component>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

【Laravel】【ダイエット支援】食事管理データベースを作成する

食事管理機能の作成に着手します。

まずはデータベースの作成。

マイグレーションを作成します。

$ php artisan make:migration create_eating_management
class CreateEatingManagement extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('eating_managements', function (Blueprint $table) {
            $table->increments('id');
            $table->date('date');
            $table->text('item');
            $table->integer('protein');
            $table->integer('ripid');
            $table->integer('carbo');
            $table->integer('calorie');
            $table->timestamps();
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });

        Schema::create('eating_management_user', function (Blueprint $table) {
            $table->integer('user_id')
                  ->foreign('user_id')
                  ->references('id')->on('users')
                  ->onDelete('cascade');
            $table->integer('eating_management_id')
                  ->foreign('eating_management_id')
                  ->references('id')->on('eating_managements')
                  ->onDelete('cascade');
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });

        Schema::create('timezones', function (Blueprint $table) {
            $table->increments('id');
            $table->text('zone');
            $table->timestamps();
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });

        Schema::create('eating_management_timezone', function (Blueprint $table) {
            $table->integer('eating_management_id')
                  ->foreign('eating_management_id')
                  ->references('id')->on('eating_managements')
                  ->onDelete('cascade');
            $table->integer('timezone_id')
                  ->foreign('timezone_id')
                  ->references('id')->on('timezones')
                  ->onDelete('cascade');
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('eating_management_timezone');
        Schema::dropIfExists('timezones');
        Schema::dropIfExists('eating_management_user');
        Schema::dropIfExists('eating_managements');
    }
}

必要な項目は、日付、時間帯、品目、タンパク質、脂質、炭水化物、カロリー。

時間帯は別テーブルで定義して、朝、昼、夜、間食から選択します。

時間帯は固定値なので、Seederで作成します。

$ php artisan make:seeder TimezoneSeeder
class TimezoneSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('timezones')->insert(['zone' => '朝',]);
        DB::table('timezones')->insert(['zone' => '昼',]);
        DB::table('timezones')->insert(['zone' => '夜',]);
        DB::table('timezones')->insert(['zone' => '間食',]);
    }
}

これらを実行して適用させます。

$ composer dump-autoload
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Package manifest generated successfully.
$ php artisan migrate
Migrating: 2020_07_10_092328_create_eating_management
Migrated:  2020_07_10_092328_create_eating_management (0.07 seconds)
$ php artisan db:seed --class=TimezoneSeeder
Database seeding completed successfully.

作成時間にNULLって入っているけど、まあいいか。