Nuxt.jsとFirebaseのFirestoreでリアルタイムダッシュボードを作った

## やりたいこと

Nuxt.jsとfirestoreを使用して、リアルタイムでダッシュボードを更新するシステムを作る。
リロードとかせずサクサクと！

## firestoreの作成

コンソールからプロジェクトを作成して設定

※あとで書く

## 準備

①firebaseをインストール

`yarn add firebase` or `npm install firebase --save`

②plugins/firebase.jsの作成

firebaseの初期設定をします！
firebaseのページの設定画面下部にある情報をそのまま貼り付けてます。
開発環境と本番環境でfirebaseのアカウントを使い分けるためにENVにしています。
`.env`で書いた環境変数名を書きます

```js:plugins/firebase.js
import firebase from 'firebase/app'
import 'firebase/firestore'

firebase.initializeApp({
  apiKey: process.env.FIRE_BASE_API_KEY,
  authDomain: process.env.FIRE_BASE_AUTH_DOMAIN,
  databaseURL: process.env.FIRE_BASE_DATABASE_URL,
  projectId: process.env.FIRE_BASE_PROJECT_ID,
  storageBucket: process.env.FIRE_BASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIRE_BASE_MESSAGING_SENDER_ID,
  appId: process.env.FIRE_BASE_APP_ID,
  measurementId: process.env.FIRE_BASE_MEASUREMENT_ID
})
const db = firebase.firestore()
export default db
```

③nuxt.config.jsへ追記

```js:nuxt.config.js
...省略

plugins: [
  ...
  { src: '~/plugins/firebase' }
],

...省略
```

これでfirestoreを使う準備が整いました！


## store/firestoreディレクトリを用意する

私はstore配下をAPI毎にディレクトリを切って、actions.js/getters.js/index.js/mutations.js
と分けて書く方法が好きです。
理由は見やすいし追いやすいからです。

今回はfirestoreの通信についてなので、firestoreとディレクトリを切ります。

まずはactions.js

```js:store/actions.js
import db from '~/plugins/firebase'
// コレクション名をセット
const fireStore = db.collection('dashboard')

export const actions = {
  GET_REALTIME_LIST({ commit }, payload) {
    fireStore
      .where('report_datetime', '>', payload.from)
      .where('report_datetime', '<', payload.to)
      .orderBy('report_datetime', 'asc')
      .orderBy('created_at', 'asc')
      .onSnapshot(
        res => {
          commit('GET_REALTIME_LIST', res)
        },
        error => {
          console.error('GET_REALTIME_LIST', error)
        }
      )
  },
  UPDATE_FIRE_STORE({ commit }, payload) {
    fireStore
      .doc(payload.id)
      .set(payload)
      .then(res => {
        commit('UPDATE_FIRE_STORE', res)
      })
      .catch(e => {
        console.error('UPDATE_FIRE_STORE', e)
      })
  }
}

export default actions
```

`const fireStore = db.collection('dashboard')`
ここで接続をするコレクション名をセットします。
今回のコレクション名は`dashboard`なのでこのように記述。

`onSnapshot `これがリアルタイム同期をするポイントです。
他にも追加されたものだけ取得、削除されたものだけ取得、とか書き方はありますが
今回のダッシュボードは全体を常に同期していたいのでこう書いてます。

また、firestoreではあまり柔軟にクエリが書けません。
between句もありません＞＜
そこで

```
.where('report_datetime', '>', payload.from)
.where('report_datetime', '<', payload.to)
```
このように書くと、between句のように使えます。

次はmutations.js


```js:mutations.js
import {
  GET_REALTIME_INCOMPLETE_LIST_FIRE_STORE,
  UPDATE_FIRE_STORE
} from '~/store/types'

export const mutations = {
  GET_REALTIME_LIST(state, content) {
    state.realtimeFireStore = content.docs.map(res => {
      const fireStore = res.data()
      fireStore.id = res.id
      return fireStore
    })
  },
  UPDATE_FIRE_STORE(state, content) {
    state.updateFireStore = content
  }
}

export default mutations
```

actionsでmutationsにcommitする際に、res.docsとやってもmutationsでmapで回す前にやっても大丈夫です。

responseではdocsの中に一覧が返ってきます。
そのまま直接参照することは出来ないので、mapで回しながらdata()とやってあげます。

`fireStore.id = res.id` ここでなぜidを入れているかというと
データを入れる際に、自動生成IDで作成しているので自動生成された一意のIDがデータ内に入っていません。

保持をしておかないとupdateで使用する際に困るので、データ内に突っ込んでます。
自動生成されたIDは、docs内にあります。IDはdata()で参照する中にはなく直接参照できるので、上記のように書きます。

続いてgetters.js


```js:getters.js
export const getters = {
  getRealtimeFireStore: state => state.realtimeFireStore,
  getUpdateFireStore: state => state.updateFireStore
}

export default getters
```

ここは特にポイントはありません

続いてindex.js

```js:index.js
import Actions from './actions'
import Getters from './getters'
import Mutations from './mutations'

export const state = () => ({
  realtimeFireStore: [],
  updateFireStore: ''
})
export const actions = Actions
export const getters = Getters
export const mutations = Mutations
```

ここも特にポイントはありません。

これでstoreに準備完了です。


### pages/dashboard.vueの作成

さて、やっと画面に取り掛かります。

```vue:pages/dashboard.vue
<template>
  <div>
    <div class="contents">
      <h1 class="title">テスト</h1>
    </div>
    <div>
      <table class="dashboard">
        <tr v-for="(dashboardRow, rowIndex) in dashboards" :key="rowIndex">
          <td v-for="(dashboard, dashboardIndex) in dashboardRow" :key="dashboard.id">
            <div class="dashboard-card">
              <input type="text" :value="dashboard.title" @blur="save(rowIndex, dashboardIndex)" />
            </div>
          </td>
        </tr>
      </table>
    </div>
  </div>
</template>

<script>
import moment from 'moment'
import { mapGetters, mapActions } from 'vuex'
import chunk from 'lodash/chunk'
import cloneDeep from 'lodash/cloneDeep'
export default {
  name: "dashboard",
  computed: {
    ...mapGetters('firestore/', [
      'getUpdateFireStore',
      'getRealtimeFireStore'
    ]),
    dashboards: {
      get(){
        const getFireStore = cloneDeep(this.getRealtimeFireStore)
        const dashboards = chunk(filterData, 3).map((dashboardItems, index) => {
          if (dashboardItems.length !== 3) {
            for (let i = 0; i < 4 - dashboardItems.length; i++) {
              dashboardItems.push({})
            }
          }
          return dashboardItems
        })
        return dashboards
      },
      set(dashboards){
        return dashboards
      }
    }
  },
  created() {
    this.getDashboard()
  },
  methods: {
    ...mapActions('firestore/', [
      'UPDATE_FIRE_STORE',
      'GET_REALTIME_LIST'
    ]),
    async getDashboard() {
      this.GET_REALTIME_LIST({
        from: moment().subtract(3, 'days').format('YYYY-MM-DD 00:00:00'),
        to: moment().format('YYYY-MM-DD 23:59:59')
      })
    },
    async save(rowIndex, dashboardIndex) {
      await this.UPDATE_FIRE_STORE(this.dashboards[rowIndex][dashboardIndex])
    },
  }
}
</script>

<style scoped>
table.dashboard {
  width: 100%;
  margin-top: 40px;
}
table.dashboard td {
  width: 30%;
  padding: 20px 14px;
}
.dashboard-card {
  border: 1px solid #cecece;
  border-radius: 4px;
  padding: 10px 6px;
  background-color: #f7f1f1;
  box-shadow: 0px 0px 9px 0px #afafaf;
}
</style>
```

直近3日のデータを取得しています。

```
created() {
  this.getDashboard()
},
```

createdでリアルタイムでgetするactions: GET_REALTIME_LIST しているメソッドを呼びます。
これで常にリアルタイム通信が可能となります。

computed内で、リアルタイムでgetしているデータを(gettersのgetRealtimeFireStore)を加工してあげます。

横3個ずつズラ〜と出したいのでlodashのchunkでわけてあげます。


カード内のテキストボックスでは、文字firestoreに入れてるデータのtitleを表示しています。
この値を変更してフォーカスを外してタイミングで更新メソッド(save)を呼んでいます。


actions:UPDATE_FIRE_STOREを呼んで、更新処理を行います。

この更新がかかると、今actions:GET_REALTIME_LISTがリアルタイムで取得しているので、
getters: getRealtimeFireStoreの値が変更されるため
computedのdashboardsが動き、画面上のカードがリアルタイムで変更されます。


### リアルタイムで動いている確認方法

方法①このlocalhostを2つ開き、画面に分けて表示して
片方で更新かけて、もう1つの方でリロードなしに変更がされれば成功です！

方法②firestoreのコンソールで直接データを変更して、リロードなしにダッシュボード内のカードが更新されれば成功です！



以上でリアルタイムダッシュボードの実装は終わりです！