作ろうと思ったきっかけ
よく退勤時、いつも退勤時間を記録し忘れてオフィスを離れてしまうのです。翌日、会社に来てから
「あれ・・・?俺、昨日何時頃に退勤したっけ・・・?」
ってわからなくなる。
というか、退勤のときに**IC端末に”ピッ”**ってするんですけどね、何時に退勤したのかわからなくなるの。
(まあ、帰る前に勤怠管理システムに入力してから帰れよって話です
けどねw)
さて、そんなわけで、スマホにメモすればよくね?って思ったので、Google Keepとかにメモはしてたのです。
しかしね、スマホで”09/21 18:50”って入力するの、結構めんどくさいです。
**だったら作ってしまえ!!!**ってことで、作ることにしました。
実際に運用しているサービス→ https://workenddays.firebaseapp.com/
コードは後ほどGitHubに掲載考えています。
今回使用した技術
Vue.js, Vue-CLI
一回、SPAで作ってみたかったのです。今までVue.js使って単純なHTMLなりは使っていましたが、NPM含め、CLI関係は難しそうでスルーしてた。いまいち使い方わからなかったけど、慣れるために比較的シンプルなものに使えるようにしたいって画策してたので使いました。
Firebase
最初はWeb API作って叩けばいいかなって思ってたけど、この辺でログインの認証システムとか作るのめんどくさいなぁって思ってた。で、Firebaseなる記事がQiita見てたら流れてきたので、それを先生にしながら作ってみました。
moment.js
時間操作のライブラリですね。これ結構使いやすいのです。
最初は、時刻を入力するフォーム使ってましたが、Bootstrap-Datetime-Pickerなど選んでいましたが、どうもうまく表示されなかったりするのがめんどくさいし、最初の時間はページ開いた時間にしたいなぁって思ったときに、これは叶えられないかなとおもったのでスルー。
また、moment.jsは”5分足す”、”5分引く”とか単純に指定するだけで時刻も日付も変化するので、これを使っていくことにしました。
BootStrap 4
CSSライブラリはBootstrapにしました。今回はデザインに中力せず、Vue-CLIとFirebaseを使うことに焦点をおいています。そのため、完成を早くするための妥協ということで。
本音:単純に手抜きしたかったので・・・
シンプルに入力するために
やってることは、他の人が作っているTodoやメモと似たようなサービスなので細かいところは省略します。
200イイねぐらいついたら書くかな・・・
こだわった点は以下の内容
- 時刻の登録はワンタップで!
- 文字入力はしたくない。
- レスポンシブデザインでスマホでもPCでも簡単に使えればいいなぁ。
- 5分刻みで入力して、なるべく会社の作業工数入力時間と、退勤時間の逆転が起きないようにしたい
さぁ、この条件を求めたら、トップ画像のようなUIになりました。
削除も確認モーダルは出しますが、それ以上のことはしていません。
今回の要点を満たすために
- inputタグを使用する場合、Datetime Pickerの選定が必要になる。うまいこと動かない場合、ハマる(初心者なため)
- 時刻は足し算と引き算だけでやりたいなぁって。
- 開いたときにすでに時刻が登録されているとラク。
- 5分刻みで、現在時刻からある程度引かれた時間がセットされているといい
- 入力のためのモーダルは使いたくない。
さて、今回の開発において、最初はBootstrap Datetime Pickerみたいなものを検索してNPM経由でインストールしたのですが、コレがまたうまいこと動かない・・・
初心者なので原因追求できないかなと思ったため、4時間程度でチャレンジをやめて、Datetime型みたいなものを制御できるライブラリを探すことにしました。
これを実現するために
解説内容は、主にmoment.jsの関数をいかに使い回すかです。
Bootstrap, Font-Awesome, Vue.jsの解説については別途・・・
UIの設計における要点
- 年、月、日、時、分に分割します。
- 各項目ごとに上下に三角を表示します。
- きれいに並べるために、Bootstrapでいろいろ指定します。
- コンポーネントの移植も考えて設計します。(今回は失敗)
- 関数が増えるのは嫌なので、引数により変化するようにしたい。
これを満たす部分は以下のコードになります。不要部分はなるべく削っております。
<template>
<div id="main" class="container">
<div class="card mt-3" v-if="user">
<div class="card-body">
<form class="create-todo">
<div class="form-group">
<div class="mt-2 d-inline-flex align-items-stretch">
<div class="text-center m-2">
<a class="mb-2" @click="setTimeUpDown('years', 1)"><i class="fas fa-caret-up" aria-hidden="true"></i></a>
<p class="m-0 p-0">{{dateFormatted.YYYY}}</p>
<a class="mt-2 pb-3" @click="setTimeUpDown('years', -1)"><i class="fas fa-caret-down" aria-hidden="true"></i></a>
</div>
<div class="text-center m-2 align-self-center">
<p class="m-0 p-0">/</p>
</div>
<div class="text-center m-2">
<a class="mb-2" @click="setTimeUpDown('months', 1)"><i class="fas fa-caret-up" aria-hidden="true"></i></a>
<p class="m-0 p-0">{{dateFormatted.MM}}</p>
<a class="mt-2 pb-3" @click="setTimeUpDown('months', -1)"><i class="fas fa-caret-down" aria-hidden="true"></i></a>
</div>
<div class="text-center m-2 align-self-center">
<p class="m-0 p-0">/</p>
</div>
<div class="text-center m-2">
<a class="mb-2" @click="setTimeUpDown('days', 1)"><i class="fas fa-caret-up" aria-hidden="true"></i></a>
<p class="m-0 p-0">{{dateFormatted.DD}}</p>
<a class="mt-2 pb-3" @click="setTimeUpDown('days', -1)"><i class="fas fa-caret-down" aria-hidden="true"></i></a>
</div>
<div class="text-center m-2">
<p class="m-2 p-2"></p>
</div>
<div class="text-center m-2">
<a class="mb-2" @click="setTimeUpDown('hours', 1)"><i class="fas fa-caret-up" aria-hidden="true"></i></a>
<p class="m-0 p-0">{{dateFormatted.HH}}</p>
<a class="mt-2 pb-3" @click="setTimeUpDown('hours', -1)"><i class="fas fa-caret-down" aria-hidden="true"></i></a>
</div>
<div class="text-center m-2 align-self-center">
<p class="m-0 p-0">:</p>
</div>
<div class="text-center m-2">
<a class="mb-2" @click="setTimeUpDown('minutes', 5)"><i class="fas fa-caret-up" aria-hidden="true"></i></a>
<p class="m-0 p-0">{{dateFormatted.mm}}</p>
<a class="mt-2 pb-3" @click="setTimeUpDown('minutes', -5)"><i class="fas fa-caret-down" aria-hidden="true"></i></a>
</div>
<!-- <date-picker v-model="date" :config="options"></date-picker> -->
</div>
<div class="mt-2 text-right">
<button class="btn btn-success" @click="addCheckout">退勤打刻</button>
</div>
</div>
</form>
</div>
</div>
<!-- 省略 -->
</div>
</template>
<script>
import { auth, database } from '@/utils/firebase'
import Works from './Works'
let workRef = database.ref('/workList')
export default {
name: 'Main',
data () {
return {
workList: [],
date: Date,
dateFormatted: {
YYYY: '',
MM: '',
DD: '',
HH: '',
mm: ''
},
email: 'No User',
user: null
}
},
// 中略
methods: {
getNowTimeAtFormatted () {
this.date = this.$moment()
var select = this.date.format('mm') % 5
// 5で割ったあまりで、5分刻みで時間を調整する
switch (select) {
case 0:
this.date.add('minutes', -5)
break
case 1:
this.date.add('minutes', -6)
break
case 2:
this.date.add('minutes', -2)
break
case 3:
this.date.add('minutes', -3)
break
case 4:
this.date.add('minutes', -4)
break
}
// 画面表示用のオブジェクトに代入
this.dateFormatted.YYYY = this.date.format('YYYY')
this.dateFormatted.MM = this.date.format('MM')
this.dateFormatted.DD = this.date.format('DD')
this.dateFormatted.HH = this.date.format('HH')
this.dateFormatted.mm = this.date.format('mm')
},
setTimeUpDown (type, select) {
// 変更する時刻と、変更量を指定すると、dateオブジェクトに対して時刻を変更する。
this.date.add(select, type)
// 画面表示用のオブジェクトに代入
this.dateFormatted.YYYY = this.date.format('YYYY')
this.dateFormatted.MM = this.date.format('MM')
this.dateFormatted.DD = this.date.format('DD')
this.dateFormatted.HH = this.date.format('HH')
this.dateFormatted.mm = this.date.format('mm')
},
// 省略
}
}
</script>
Moment.jsの操作において、時刻の演算はDate
型に対して、指定します。
起動時、現在時刻を取得して、Date型のオブジェクトにセットします。
コレに対して、数分引いて、5分刻みになるようにしています。
さて、肝心のボタン操作にて時刻を変更する場合ですが、イベントが発火するポイントは10箇所あります。
なるべく複雑にならないように引数で指定していきたいと思います。
this.date.add(select, type)
ここにセットする種別は
'years',
'months',
'days',
'hours',
'minutes'
となります。Moment.jsはシンプルで使いやすいですね。
あとは、増減したい数字をセットするだけ。命名規則から、そのままHTML側に記載した場合、どの項目を操作したいのかが明確になるかなと判断したので、タグから呼び出す関数の引数に対しても使っています。
表示内容については、表示用のオブジェクトにセットしています。本来は直接呼ぶこともできるかと思うのですが、表示用のオブジェクトを直接呼ぶのはあんまり好きではないため、バッファをかませるような作り方になっています。
バッファの内容をバインディングしているので、デバッグ時もコンソールを割と見やすかったですよ。
また、Firebaseにアップした後に編集する昨日も考えていたため、このような設計になっています。
なお、その機能を実装することはなかった・・・
実装しなかった理由・・・
ミスったら消して登録しなおせばよくね・・・?
と、簡単に書いていますが気がついたらmoment.jsの解説で終わってしまいました・・・