【Script SetUp】dayjsを使ったカレンダー作成【TypeScript】

# 開発環境
MacBook Air (M1, 2020)
npm 8.18.0
"vue": "^3.2.13"
"dayjs": "^1.11.5"

# 本記事の内容

dayjsというライブラリを使ったカレンダーを作成します。

以下が目次です。
* 使用するものの作成
* カレンダーの作成
* より詳しい紹介

では解説していきます。

# 使用するものの作成
①使用するイベントデータ
②使用する型定義

## 使用するイベントデータ
```:data
events:[
  { id: 1, name: "宿題", date: "2022-10-01", color:"blue"},
  { id: 2, name: "遊び", date: "2022-10-10", color:"limegreen"},
  { id: 3, name: "ミーティング", date: "2022-10-15", color:"deepskyblue"},
  { id: 4, name: "", date: "2022-10-20", color:"dimgray"},
  { id: 5, name: "デート", date: "2022-10-31", color:"navy"},
]
```

## 使用する型定義
型を用意します。
```typescript:src/types/index.ts
export type EventItem={
    id: number,
    pet_id: number,
    health: string,
    detail: string,
    start: Date,
    end: Date,
    color: "blue"| "red"|"limegreen"|"deepskyblue"|"dimgray"|"navy"|"orange"|"teal"|"royalblue",
    isEditable: boolean
}

export type DateItem = {
    date: number
    month: string
    dayEvents: EventItem[]
}
```
# カレンダーの作成
手順は以下の11個です。
①カレンダーの最初の日を取得する。
②カレンダーの最後の日を取得する。
③一ヶ月の週の数分の配列の作成する。
④③で作成した配列を取得する。
⑤フォーマットを整える
⑥前の月を表示する。
⑦後ろの月を表示する。
⑧数字から曜日に変更を行うメソッドを定義する。
⑨背景色を分けるためのプロパティを作成する。
➉イベントを取得する。
⑪日付が一致するイベントを取得し、配列として格納するメソッドする。

では以下のコードを見ていきましょう。
```vuejs: src/views/CalendarView.vue
<script setup lang="ts">
import dayjs from 'dayjs'
import { ref, computed } from 'vue'
import { EventItem } from '@/types/event';
import { useStore } from '../store'

const currentDate = ref(dayjs())

//①カレンダーの最初の日を取得する。
const getStartDate = () => {
    //月の初めの日を取得する
    let date = dayjs(currentDate.value).startOf("month");
    const youbiNum = date.day();
    //月の初めの日から日曜日までの差を算出し、日曜日の日を取得。
    return date.subtract(youbiNum, "days");
}
//②カレンダーの最後の日を取得する。
const getEndDate = () => {
    //月の最後の日を取得
    let date = dayjs(currentDate.value).endOf("month");
    const youbiNum = date.day();
    //月の初めの日から土曜日までの差を算出し、土曜日の日を取得。
    return date.add(6 - youbiNum, "days");
};
//③一ヶ月の週の数分の配列の作成
const getCalendar = () => {
  let startDate: dayjs.Dayjs;
  //月の初めの日
  startDate = getStartDate();
  //月最後の日
  const endDate = getEndDate();
  //カレンダーの縦軸を計算
  const weekNumber = Math.ceil(endDate.diff(startDate, "days") / 7);
  
  //空配列を用意
  let calendars: DateItem[][] = [];
  let calendarDate: dayjs.Dayjs;
  calendarDate = getStartDate();

//7日分の日にちデータをループで取得してweekRowにpush。そしてweekRowの配列をcalendarsにpush。それをカレンダーの縦軸回数繰り返す処理
  for (let week = 0; week　<　weekNumber; week++){
    const weekRow = [];
    for (let day = 0;  day < 7; day++) {
      let dayEvents: EventItem[] = getDayEvents(calendarDate)
      weekRow.push({
        date: calendarDate.get("date"),
        month: calendarDate.format("YYYY-MM")//背景色を分けるため追加
        dayEvents
      })
      calendarDate = calendarDate.add(1, "days");
    }
    calendars.push(weekRow);
  }
  return calendars;
}

//④③で作成した配列を取得
const calendars = computed(()=>{
  return getCalendar()
})

//⑤フォーマットを整える
const displayMonth = computed(()=>{
  return currentDate.value.format('YYYY[年]M[月]')
})

//⑥前の月を表示
const prevMonth = () => {
  currentDate.value= dayjs(currentDate.value).subtract(1, "month")
}

//⑦後ろの月を表示
const nextMonth = () => {
  currentDate.value = dayjs(currentDate.value).add(1, "month")
}

//⑧数字から曜日に変更を行うメソッドを定義
const youbi = (dayIndex: number) => {
  const week = ["日", "月", "火", "水", "木", "金", "土"];
  return week[dayIndex];
}

//⑨背景色を分けるためのプロパティ
const currentMonth= computed(()=>{
  return currentDate.value.format('YYYY-MM')
})

const store = useStore()

//⑩イベントを取得
const events = computed<EventItem[]>(()=>{
  return store.getters['calendar/getEvents']
})

//⑪日付が一致するイベントを取得し、配列として格納するメソッド
const getDayEvents = (date: dayjs.Dayjs) => {
  return events.value.filter((event: EventItem)=> {
    let eventDate = dayjs(event.start).format('YYYY-MM-DD')
    let Date = date.format('YYYY-MM-DD')
    if(eventDate == Date) return true;
  });
}

</script>
<template>
  <div>
    <div class="content">
      <p>{{currentDate}}</p>
    <h3></h3>
    <h2>現在の日付：{{ displayMonth }}</h2>
    <div class="button-area">
      <button @click="prevMonth" class="button">前の月</button>
      <button @click="nextMonth" class="button">次の月</button>
    </div>
    <div class="calendar">
      <div class="calendar-weekly">
        <div class="calendar-youbi" v-for="n in 7" :key="n">
          {{ youbi(n-1) }}
        </div>
      </div>
      <div class="calendar-weekly" v-for="(week, index) in calendars" :key="index">
        <div class="calendar-daily" :class="{outside: currentMonth !== day.month}" v-for="(day, index) in week" :key="index">
          <div class="calendar-day">
            <p>{{ day.date }}</p>
          </div>
          <!--currnetMonthの値とday.monthの値が異なる場合のみstyleを適用-->
          <div v-for="dayEvent in day.dayEvents" :key="dayEvent.id" >
        <div class="calendar-event" :style="`background-color:${dayEvent.color}`" >
          <p>{{ dayEvent.health }}</p>
        </div>
      </div>
        </div>
      </div>
    </div>
  </div>
  </div>
</template>

<style>
 /* 全体調整*/
.content{
 /* 中央よせ*/
  margin:2em auto;
 /* 大きさ調整*/
  width:900px;
}
.button-area{
  margin:0.5em 0;
}
 /* ボタン調整*/
.button{
  padding:4px 8px;
  margin-right:8px;
}
.calendar{
 /* 上横線*/
  border-top:1px solid #E0E0E0;
 /* カレンダーフォントサイズ*/
  font-size:0.8em;
}
.calendar-weekly{
 /* 週ごとに横並び*/
  display:flex;
 /* 左縦線*/
  border-left:1px solid #E0E0E0;
}

/* 日にちのスタイル*/
.calendar-daily{
  /* 横幅*/
  flex:1;
 /* 大きさ調節*/
  min-height:125px;
 /* 右縦線*/
  border-right:1px solid #E0E0E0;
 /* 下横線*/
  border-bottom:1px solid #E0E0E0;
}

/* 曜日のスタイル*/
.calendar-youbi{
  /* 横幅調節*/
  flex:1;
  /* 縦線*/
  border-right:1px solid #E0E0E0;
}

/* 背景色*/
.outside{
  background-color: #f7f7f7;
}
 /* イベント調整*/
.calendar-event{
 /* フォント色付け*/
  color:white;
  line-height:25px;
 /* 丸みを帯びさせる*/
  border-radius:4px;
}
</style>
```
# より詳しい解説
ちょっとわかりづらい、8、9、10の3つのの3つについてより詳しい解説していきます。

## 数字から曜日に変更を行うメソッドを定義
タイトルの通り、数字を曜日に変更するメソッドです。
v-forを使い、7を順番に展開。
youbiメソッドを実行して数字を曜日に変更しています。
forで展開する場合、1から始まるので、-1としています。

```vuejs: src/views/CalendarView.vue
<script setup lang="ts">
const youbi = (dayIndex: number) => {
  const week = ["日", "月", "火", "水", "木", "金", "土"];
  return week[dayIndex];
}
</script>
<template>
    <div class="calendar">
      <div class="calendar-weekly">
        <div class="calendar-youbi" v-for="n in 7" :key="n">
          {{ youbi(n-1) }}
        </div>
</template>
```
## 背景色を分けるためのプロパティ
ここでの目的は、currentMonthとの比較です。
そのためにフォーマットを整えます。

```vuejs: src/views/CalendarView.vue
<script setup lang="ts">
const currentMonth= computed(()=>{
  return currentDate.value.format('YYYY-MM')
})
const getCalendar = () => {
    //省略
  
  let calendars: DateItem[][] = [];
  let calendarDate: dayjs.Dayjs;
  calendarDate = getStartDate();

  for (let week = 0; week　<　weekNumber; week++){
    const weekRow: DateItem[] = [];
    for (let day = 0;  day < 7; day++) {
      let dayEvents: EventItem[] = getDayEvents(calendarDate)
      weekRow.push({
        date: calendarDate.get("date"),
        month: calendarDate.format("YYYY-MM")//背景色を分けるため追加
        dayEvents
      })
      calendarDate = calendarDate.add(1, "days");
    }
    calendars.push(weekRow);
  }
  return calendars;
}
</script>
<template>
    <!--currentMonthとday.monthが異なる場合のみcssを適用-->
    <div class="calendar-daily" :class="{outside: currentMonth !== day.month}" v-for="(day, index) in week" :key="index">
        <div class="calendar-day">
            <p>{{ day.date }}</p>
        </div>
</template>
```

## 日付が一致するイベントを取得し、配列として格納するメソッド
イベントという配列に格納されている情報を、カレンダーの日付と一致する場合に渡す処理を行なっています。

```vuejs: src/views/CalendarView.vue
<script setup lang="ts">

const getCalendar = () => {
    //省略
  
  let calendars: DateItem[][] = [];
  let calendarDate: dayjs.Dayjs;
  calendarDate = getStartDate();

  for (let week = 0; week　<　weekNumber; week++){
    const weekRow: DateItem[] = [];
    for (let day = 0;  day < 7; day++) {
      let dayEvents: EventItem[] = getDayEvents(calendarDate)
      weekRow.push({
        date: calendarDate.get("date"),
        month: calendarDate.format("YYYY-MM")//背景色を分けるため追加
        dayEvents
      })
      calendarDate = calendarDate.add(1, "days");
    }
    calendars.push(weekRow);
  }
  return calendars;
}
//渡されてきたcurrentDateとイベントに格納されている日付データと一致する場合にその情報を配列にして渡す。
const getDayEvents = (date: dayjs.Dayjs) => {
  return events.value.filter((event: EventItem)=> {
    let eventDate = dayjs(event.start).format('YYYY-MM-DD')
    let Date = date.format('YYYY-MM-DD')
    if(eventDate == Date) return true;
  });
}
</script>
```