あと一つ大きなメイン機能であるイベント機能が残っていたのでそちらを作成した。
色々見てみた結果、とりあえず全部FullCalendarに置き換えればいいだろうと言う結論に至った。
FullCalendar - JavaScript Event Calendar
期間や範囲切り替えもあるし、これだけで一通りまかなえる気がする。
本家にはない(?)が、予定メニューにも色を付けられるようにした。
<template>
<div class="row">
<div class="col-12">
<div class="sample">
<span class="event-color-sample" v-bind:style="{backgroundColor: currentBgColor.hex, color: currentTextColor.hex}">サンプル</span>
</div>
</div>
<div class="col-12 col-sm-4">
<div>背景色</div>
<swatches-picker v-model="currentBgColor"></swatches-picker>
</div>
<div class="col-12 col-sm-4">
<div>文字色</div>
<swatches-picker v-model="currentTextColor"></swatches-picker>
</div>
<input type="hidden" :name="bg_color_name" :value="currentBgColor.hex">
<input type="hidden" :name="text_color_name" :value="currentTextColor.hex">
</div>
</template>
<style scoped>
div.row {
margin-bottom: 20px;
}
</style>
<script>
import { Swatches } from 'vue-color'
export default {
props: ['bg_color_name', 'text_color_name', 'bg_color', 'text_color'],
components: {
'swatches-picker': Swatches,
},
data () {
return {
currentBgColor: {hex: this.bg_color === undefined ? '#3F51B5' : this.bg_color},
currentTextColor: {hex: this.text_color === undefined ? '#FFFFFF' : this.text_color},
}
},
methods: {
}
}
</script>
一覧もvuedraggableを使ってドラッグ&ドロップで簡単に並び替えできるようにした。
面倒かと思ったが、元々のテンプレートをとりあえずコピーから始められるのでそれほどでもなかった。
<template>
<table class="table">
<thead>
<tr>
<th></th>
<th>予定メニュー名</th>
<th></th>
</tr>
</thead>
<draggable v-model="scheduleCategories" :element="'tbody'" :options="{handle: '.handle'}" @end="onEnd">
<tr v-for="scheduleCategory in scheduleCategories" :key="scheduleCategory.id">
<td class="handle"><i class="material-icons">drag_handle</i></td>
<td>
<span
v-text="scheduleCategory.name"
class="event-color-sample"
v-bind:style="{backgroundColor: scheduleCategory.bg_color, color: scheduleCategory.text_color}"
></span>
</td>
<td class="text-right">
<span><a :href="`/${group_id}/schedule-categories/${scheduleCategory.id}/edit`" class="btn btn-default btn-xs">編集</a></span>
<span>
<a
href="#"
data-confirm="削除してよろしいですか?"
:data-csrf="csrf"
data-method="delete"
:data-to="`/${group_id}/schedule-categories/${scheduleCategory.id}`"
rel="nofollow"
class="btn btn-danger btn-xs"
>削除<div class="ripple-container"></div></a>
</span>
</td>
</tr>
</draggable>
</table>
</template>
<style scoped>
.handle {
cursor: crosshair;
}
</style>
<script>
import draggable from 'vuedraggable'
import axios from 'axios'
export default {
props: ['group_id', 'schedule_categories'],
components: {draggable},
data () {
return {
scheduleCategories: this.schedule_categories,
csrf: document.querySelector('meta[name=csrf]').getAttribute('content'),
}
},
methods: {
onEnd() {
axios.put(`/${this.group_id}/schedule-categories/update-order`, {
ids: this.scheduleCategories.map(c => c.id),
});
}
}
}
</script>
保存側。
def update_schedule_categories_order(group_id, ids) do
Enum.with_index(ids)
|> Enum.each(fn{id, index} ->
schedule_category = get_schedule_category!(id, group_id)
update_schedule_category(schedule_category, %{"display_order" => index + 1})
end)
end
こちらも面倒かと思ったが、データの取得はコールバックに作ればいいだけだったので非常に楽だった。
<template>
<div id="calendar">
</div>
</template>
<style scoped>
</style>
<script>
import axios from 'axios'
import moment from 'moment'
export default {
props: ['group_id', 'month'],
data () {
return {
currentEvents: [],
}
},
mounted() {
if (this.month !== undefined) {
}
$('#calendar').fullCalendar({
locale: 'ja',
header: {
right: 'month,agendaWeek,agendaDay today prev,next'
},
views: {
month: {
titleFormat: 'YYYY年 MMMM',
},
},
buttonText: {
today: '今日',
month: '月',
week: '週',
day: '日',
},
firstDay: 1,
timeFormat: 'HH:mm',
defaultDate: this.getDefaultDate(),
events: this.loadEvents.bind(this),
dayClick: this.dayClick.bind(this),
eventClick: this.eventClick.bind(this),
});
},
methods: {
dayClick(date, jsEvent, view) {
const dateText = date.format('YYYY-MM-DD');
if (view.name == 'month') {
location.href = `/${this.group_id}/schedules/new?date=${dateText}`;
} else {
const timeText = date.format('HH:mm:ss');
location.href = `/${this.group_id}/schedules/new?date=${dateText}&time=${timeText}`;
}
},
eventClick(calEvent, jsEvent, view) {
location.href = `/${this.group_id}/schedule-posts/${calEvent.schedule.id}`;
},
loadEvents(start, end, timezone, callback) {
const startDate = start.format('YYYY-MM-DD');
const endDate = end.format('YYYY-MM-DD');
axios.get(`/${this.group_id}/schedules/events/${startDate}/${endDate}`)
.then(response => {
this.$emit('GET_AJAX_COMPLETE');
callback(response.data);
});
},
getDefaultDate() {
if (this.month !== undefined) {
return moment(this.month + '-01');
} else {
return moment();
}
}
}
}
</script>
取得側。
こういう重そうな範囲検索は、大規模サービスだとどう実装してるのか気になる。
日毎にデータを分けてるのかな。(日を子データにしてるとか)大変そう。
def list_schedules_for_range(group_id, start_date, end_date) do
query = from s in Schedule,
where: s.group_id == ^group_id
and (
(s.start_date < ^start_date and ^end_date < s.end_date)
or (^start_date <= s.start_date and s.start_date <= ^end_date)
or (^start_date <= s.end_date and s.end_date <= ^end_date)
)
and is_nil(s.deleted_at)
Repo.all(query)
|> Repo.preload(:schedule_category)
end
リピートの予定とか放置してる。
あと、設備とかどこで使ってるんだろうと思ったら、アクセスの方法によって登録できたりするっぽい。
完全に抜けてる。
わかりづらいなここは。急にグループ関係なしの画面になるし意味が分からない。
テスト放置なので整備しようかと思う。
そっちの方が面白くて書くこと多かったりするかもしれない。
ソース書いてるのVueばっかりだし。
第2回 | サイボウズLiveを作る-第2回-グループ登録まで |
第3回 | サイボウズLiveを作る-第3回-トピック登録まで |
第4回 | サイボウズLiveを作る-第4回-Todoをざっと |
第5回 | サイボウズLiveを作る-第5回-グループへ参加 |
第6回 | サイボウズLiveを作る-第6回-イベント作成 |
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント