Vuetifyのv-calendarを使って献立カレンダーを作ってみた

## やりたいこと
* 日々の献立表をカレンダーを使って表示させる
* カレンダー表示時にAPIを実行し、献立を取得する

今回はVuetifyを使っていたので、v-calendarを使って実現しました。
Vuetifyの環境構築については[こちらのページ](https://qiita.com/tToyoshima/items/a5da2fd2162fb3c28580)に記載しています。

## ソースコードの構成
構成としては`src/views`配下にカレンダー表示用ページを作り、
実際のカレンダー表示は`src/component`にコンポーネント化し、viewに組み込みます。

## カレンダーコンポーネント
まずは、カレンダー表示用コンポーネントを作成します。
```src/component/CalendarComponent.vue
<template>
  <v-row justify="center" style="height: 600px">
    <v-col cols="10">
      <v-sheet tile height="6vh" color="grey lighten-3" class="d-flex align-center">
        <v-btn outlined small class="ma-4" @click="setToday">
          今日
        </v-btn>
        <v-btn icon @click="$refs.calendar.prev()">
          <v-icon>mdi-chevron-left</v-icon>
        </v-btn>
        <v-btn icon @click="$refs.calendar.next()">
          <v-icon>mdi-chevron-right</v-icon>
        </v-btn>
        <v-toolbar-title>{{ title }}</v-toolbar-title>
      </v-sheet>
      <v-calendar
        ref="calendar"
        v-model="value"
        locale="ja-jp"
        :events="events"
        :day-format="(timestamp) => new Date(timestamp.date).getDate()"
        :month-format="(timestamp) => (new Date(timestamp.date).getMonth() + 1) + ' /'"
        @click:event="clickEvent"
        @click:date="clickDay"
      ></v-calendar>
    </v-col>
  </v-row>
</template>

<script>
import { mapActions } from "vuex";
import moment from "moment";

export default {
  name: "CalendarComponent",
  data() {
    return {
      value: moment().format("yyyy-MM-DD"),
    };
  },
  props: {
    events: [],
  },
  methods: {
    ...mapActions("calendar-store", [
      "setSelectedDate",
    ]),
    setToday() {
      this.value = moment().format("yyyy-MM-DD");
    },
    clickEvent({ event }) {
      this.$emit("clickEvent");
      this.setSelectedDate(event.start);
    },
    clickDay({ date }) {
      this.$emit("clickDay");
      this.setSelectedDate(date);
    },
  },
  computed: {
    title() {
      return moment(this.value).format("yyyy年 M月");
    },
  },
};
</script>
```

以下で細かく説明します。
### カレンダーの表示
```vuejs
<v-calendar
  :events="events"
></v-calendar>
```
カレンダーに表示するデータは`:events`にて渡します。
今回はページ側から受け渡しするために、popsに定義しました。
```vuejs
props: {
  events: [],
},
```

### 月の遷移ボタン
```vuejs
<v-btn outlined small class="ma-4" @click="setToday">
  今日
</v-btn>
<v-btn icon @click="$refs.calendar.prev()">
  <v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn icon @click="$refs.calendar.next()">
  <v-icon>mdi-chevron-right</v-icon>
</v-btn>
<v-toolbar-title>{{ title }}</v-toolbar-title>
```
上記はカレンダーのツールバーに「今日」「＜」「＞」の3つのボタンと「年月」を並べて表示させています。

```vuejs
<v-calendar
  v-model="value"
  :day-format="(timestamp) => new Date(timestamp.date).getDate()"
  :month-format="(timestamp) => (new Date(timestamp.date).getMonth() + 1) + ' /'"
></v-calendar>
```
`v-model`に表示させる日付を渡します。
`day-format`と`month-format`は日本語表記に変更しています。

```vuejs
data() {
  return {
    value: moment().format("yyyy-MM-DD"),
  };
},
```
`v-model`に渡す`value`は`data()`に定義し、初期値は本日日付を設定しています。（momentを使用）

```vuejs
methods: {
  setToday() {
    this.value = moment().format("yyyy-MM-DD");
  },
```
「今日」ボタンを押した場合は`value`値に本日日付が設定され、今日のカレンダーが表示されます。
`v-btn`の`$refs.calendar.prev()`および`@click="$refs.calendar.next()`は
`value`にそれぞれ前月末日、翌月初日が設定され、カレンダーが遷移します。

```vuejs
computed: {
  title() {
    return moment(this.value).format("yyyy年 M月");
  },
},
```
ツールバーの日付部分に表示されている年月を算出しています。

### イベント処理
```vuejs
<v-calendar
  @click:event="clickEvent"
  @click:date="clickDay"
></v-calendar>
```
`event`はカレンダーのイベントをクリックした際に実行されるメソッドです。
`date`はカレンダーの日付をクリックした際に実行されるメソッドです。

```vuejs
method: {
  clickEvent({ event }) {
    this.$emit("clickEvent");
    this.setSelectedDate(event.start);
  },
  clickDay({ date }) {
    this.$emit("clickDay");
    this.setSelectedDate(date);
  },
}
```
どちらも親コンポーネントのメソッドをemitするようにしました。
`this.setSelectedDate();`は日付をストアに保持しています。
（今回はストアの説明は省略します）

## カレンダービュー

次に、コンポーネントの親である、カレンダー表示用のviewを作成します。
```src/views/Calendar.vue
<template>
  <div id="calendar">
    <v-container>
      <CalendarComponent
        v-on:clickDay="onCategory"
        v-on:clickEvent="onMenuOfDay"
        :events="schedule"
      ></CalendarComponent>
    </v-container>
  </div>
</template>

<script>
import CalendarComponent from "@/components/CalendarComponent";
import MenuOfDayDialogComponent from "@/components/MenuOfDayDialogComponent";

import { mapActions, mapGetters } from "vuex";

export default {
  name: "Calendar",
  components: {
    CalendarComponent,
    MenuOfDayDialogComponent,
  },
  data() {
    return {
      isShowMenuOfDay: false,
      schedule: [],
    };
  },
  async created() {
    Promise.all([
      this.getSchedule().then((response) => {
        this.schedule = response.data;
        this.setScheduleList(response.data);
      }),
    ]);
  },
  methods: {
    ...mapActions("calendar-store", [
      "getSchedule",
      "setScheduleList",
    ]),
    onMenuOfDay() {
      this.isShowMenuOfDay = true;
    },
    onMenuOfDayClose() {
      this.isShowMenuOfDay = false;
    },
  },
};
</script>
```

以下で細かく説明します。
### カレンダーコンポーネント
```vuejs
<template>
  <div id="calendar">
    <v-container>
      <CalendarComponent
        v-on:clickDay="onCategory"
        v-on:clickEvent="onMenuOfDay"
        :events="schedule"
      ></CalendarComponent>
    </v-container>
  </div>
</template>
```
カレンダーコンポーネントを使用しています。
`v-on`にてカレンダーの日付およびイベントがクリックされた際に、emitされるメソッドを定義しています。
カレンダーコンポーネントの`events`に`schedule`を渡しています。

### 初期表示
```vuejs
async created() {
  Promise.all([
    this.getSchedule().then((response) => {
      this.schedule = response.data;
      this.setScheduleList(response.data);
    }),
    this.getMenu().then((response) => {
      this.setMenuList(response.data);
    }),
  ]);
},
```
カレンダービューファイルが構成された際に、APIにて献立表およびメニューリストを取得しています。
APIはストア経由で取得されるようにしています。（ストアの説明は省略します）
献立表は`schedule`に保持されます。

なお、APIで返却される情報は以下のようになっています。
```json
[
    {
        "name": "玉子雑炊",
        "start": "2022-05-01",
        "end": "2022-05-01",
        "color": "red",
    },
    {
        "name": "麻婆春雨",
        "start": "2022-05-09",
        "end": "2022-05-09",
        "color": "blue",
    },
    {
        "name": "小松菜ときのこのお浸し",
        "start": "2022-05-10",
        "end": "2022-05-10",
        "color": "green",
    },
]
```
* name: イベントに表示される名前
* start: イベントの開始日
* end: イベントの終了日
* color: イベントの背景色

通常のカレンダーの使い方としてはイベントの開始日と終了日をstartとendにしますが、
今回は同日を設定することで、その日のみにメニューが表示されるようにしました。

### イベント処理
```vuejs
onMenuOfDay() {
  this.isShowMenuOfDay = true;
},
onMenuOfDayClose() {
  this.isShowMenuOfDay = false;
  });
},
```
イベントがクリックされた際のメソッドの挙動です。
カレンダー機能はイベントが多いと`2 more`のように省略されるため、
今回はダイアログでその日のメニュー一覧が表示されるようにしました。
`MenuOfDayDialogComponent`がダイアログで表示されるようになっていますが、割愛します。

## 完成
下記のようなカレンダーが表示されます。
![スクリーンショット 2022-06-03 23.47.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529227/4a47f519-0dd3-e756-b491-471347652c10.png)

今回は省略しましたが、イベントをクリックするとその日のメニューが表示されます。
![スクリーンショット 2022-06-03 23.55.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529227/50569123-237c-d07f-1d9e-50983dca293b.png)