Skip to content

Instantly share code, notes, and snippets.

@nutchy
Created February 27, 2020 13:39
Show Gist options
  • Save nutchy/80ebed644d123231084337b730e9f5b0 to your computer and use it in GitHub Desktop.
Save nutchy/80ebed644d123231084337b730e9f5b0 to your computer and use it in GitHub Desktop.
<template>
<b-container class="container-content form-container">
<div class="page-header">
<h4 class="page-header-text">{{ $t('pages.booking.book_one_time') }}</h4>
</div>
<b-row>
<b-col xl="6">
<b-row no-gutters class="custom-input-datetime">
<b-col cols="12">
<div v-if="isBookingOneTime">
<small>{{ $t('form_label.maid_selection_method') }}</small>
<div class="tabs-select-maid">
<b-tabs pills fill content-class="mt-3" v-model="bookingForm.method">
<b-tab :title="$t('form_label.auto_select_maid')" active> </b-tab>
<b-tab :title="$t('form_label.manual_select_maid')">
<b-form-group v-if="bookingForm.method">
<b-row>
<b-col v-if="bookingForm.maid">
<maid-detail :detail="bookingForm.maid"></maid-detail>
</b-col>
<b-col cols="auto" v-if="!bookingForm.maid">
<img src="../../assets/images/preview-upload.png" alt />
</b-col>
<b-col v-if="!bookingForm.maid">
<h6>{{ $t('form_label.please_select_the_maid') }}</h6>
<b-button variant="success" block @click="chooseMaid">{{
$t('button.choose_maid')
}}</b-button>
</b-col>
<b-col class="text-right">
<b-link @click="chooseMaid" v-if="bookingForm.maid">
<i class="far fa-edit" style="font-size: 1.25em;"></i>
</b-link>
</b-col>
</b-row>
</b-form-group>
</b-tab>
</b-tabs>
</div>
</div>
<div v-else>
<!-- Booking Detail Actions -->
<b-form-group>
<p>{{ isHistoryBooking ? `Maid` : `Selected Maid` }}</p>
<b-row>
<b-col v-if="bookingForm.maid">
<maid-detail :detail="bookingForm.maid"></maid-detail>
</b-col>
</b-row>
</b-form-group>
</div>
</b-col>
<b-col cols="6">
<b-form-group :label="$t('form_label.pick_a_date')">
<b-input-group>
<b-form-input
class="input-date"
placeholder="Pick a date from calendar"
disabled
:value="calendarValue"
></b-form-input>
<b-input-group-append>
<b-button variant="link" class="input-fa-calendar">
<img :src="require('../../assets/images/icons/datepicker.png')" alt="" />
</b-button>
</b-input-group-append>
</b-input-group>
<small>Pick a date from calendar</small>
</b-form-group>
</b-col>
<b-col cols="6">
<b-form-group :label="$t('form_label.start')">
<v-select
:options="timeOptions"
v-model="bookingForm.time"
class="custom-v-select"
:disabled="!bookingForm.date || isHistoryBooking"
/>
</b-form-group>
</b-col>
</b-row>
</b-col>
<b-col xl="6">
<b-form-group>
<!-- Default Calendar -->
<v-calendar
:locale="$i18n.locale"
ref="calendar"
class="v-calendar-custom"
:min-date="new Date()"
:attributes="defaultCalendarAttrs"
is-expanded
@dayclick="onSelectedDate"
@update:to-page="onMonthChanged"
v-if="bookingForm.method === 0"
></v-calendar>
<!-- Maid's Calendar -->
<v-calendar
:locale="$i18n.locale"
ref="calendar"
class="v-calendar-custom"
:attributes="maidCalendarAttrs"
is-expanded
@dayclick="onSelectedDate"
@update:to-page="onMonthChanged"
v-else
></v-calendar>
<CalendarDetail
v-if="shouldShowCalendarDetail"
:selectedDate="selectedDate"
:maidCalendars="maidCalendarAvailableTimes"
/>
</b-form-group>
</b-col>
</b-row>
<b-row class="mb-2">
<b-col xl="6">
<address-select
v-model="bookingForm.location"
:disabled="isHistoryBooking"
></address-select>
</b-col>
<b-col xl="6">
<b-form-group :label="$t('form_label.accommodation')">
<v-select
class="custom-v-select"
:options="options.accommodation"
v-model="bookingForm.accommodation"
:disabled="isHistoryBooking"
/>
</b-form-group>
</b-col>
<b-col xl="6">
<b-form-group :label="$t('form_label.duration')" :description="recommendDurationText">
<v-select
class="custom-v-select"
:options="durationOptions"
v-model="bookingForm.duration"
:disabled="isHistoryBooking"
/>
</b-form-group>
</b-col>
<b-col xl="6">
<b-form-group :label="$t('form_label.services')">
<v-select
class="custom-v-select"
:options="options.services"
v-model="bookingForm.services"
:disabled="isHistoryBooking"
/>
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col xl="6">
<b-form-group :label="$t('form_label.additional_info')">
<b-form-checkbox-group
v-model="bookingForm.additional"
:options="options.additional"
:disabled="isHistoryBooking"
/>
</b-form-group>
<b-form-group>
<b-textarea
:placeholder="$t('form_label.additional_placeholder')"
rows="4"
v-model="bookingForm.description"
:disabled="isHistoryBooking"
></b-textarea>
</b-form-group>
</b-col>
<b-col xl="6">
<div class="d-flex justify-content-between">
<p class="m-0">Total ( THB - ฿ )</p>
<p class="m-0 text-dark">฿{{ grandTotal }}</p>
</div>
<hr />
<div v-if="isBookingOneTime">
<!-- <small>{{ $t('form_label.maid_selection_method') }}</small> -->
<!-- <div class="tabs-select-maid">
<b-tabs pills fill content-class="mt-3" v-model="bookingForm.method">
<b-tab :title="$t('form_label.auto_select_maid')" active>
<b-form-checkbox
id="agreement-checkbox"
v-model="isAgreePolicy"
:value="true"
:unchecked-value="false"
class="my-2"
>
<span @click="openServiceModal" style="cursor: pointer;">
{{ $t('form_label.service_policy_scope_of_service') }}
</span>
</b-form-checkbox>
<p>
will let us choose recommended maids in your vicinity for you. We recommend this
option for new users.
</p>
</b-tab>
<b-tab :title="$t('form_label.manual_select_maid')">
<b-form-group v-if="bookingForm.method">
<b-row>
<b-col v-if="bookingForm.maid">
<maid-detail :detail="bookingForm.maid"></maid-detail>
</b-col>
<b-col cols="auto" v-if="!bookingForm.maid">
<img src="../../assets/images/preview-upload.png" alt />
</b-col>
<b-col v-if="!bookingForm.maid">
<h6>{{ $t('form_label.please_select_the_maid') }}</h6>
<b-button variant="success" block @click="chooseMaid">{{
$t('button.choose_maid')
}}</b-button>
</b-col>
<b-col class="text-right">
<b-link @click="chooseMaid" v-if="bookingForm.maid">
<i class="far fa-edit" style="font-size: 1.25em;"></i>
</b-link>
</b-col>
</b-row>
</b-form-group>
<b-form-checkbox
id="agreement-checkbox"
v-model="isAgreePolicy"
:value="true"
:unchecked-value="false"
class="my-2"
>
<span @click="openServiceModal" style="cursor: pointer;">
{{ $t('form_label.service_policy_scope_of_service') }}
</span>
</b-form-checkbox>
</b-tab>
</b-tabs>
</div> -->
<div>
<b-form-checkbox
id="agreement-checkbox"
v-model="isAgreePolicy"
:value="true"
:unchecked-value="false"
class="my-2"
>
<span @click="openServiceModal" style="cursor: pointer;">
{{ $t('form_label.service_policy_scope_of_service') }}
</span>
</b-form-checkbox>
<p>
will let us choose recommended maids in your vicinity for you. We recommend this
option for new users.
</p>
</div>
<b-button variant="success" size="lg" block @click="bookNow" :disabled="!canBooking">{{
$t('button.book_now')
}}</b-button>
</div>
<div v-else>
<!-- Booking Detail Actions (move to top right) -->
<!-- <b-form-group>
<p>{{ isHistoryBooking ? `Maid` : `Selected Maid` }}</p>
<b-row>
<b-col v-if="bookingForm.maid">
<maid-detail :detail="bookingForm.maid"></maid-detail>
</b-col>
</b-row>
</b-form-group>
<hr /> -->
<b-row v-if="!isHistoryBooking">
<b-col>
<b-button variant="danger" class="py-2" block @click="toggleCancelModal"
>Cancel Booking</b-button
>
</b-col>
<b-col>
<b-button variant="success" class="py-2" block @click="doUpdateBooking"
>Update</b-button
>
</b-col>
</b-row>
<div class="my-4 d-flex justify-content-center align-items-center">
<button @click="onClose" class="btn-close">
<i class="fas fa-times" style="font-size: 1.25em;"></i>
Close
</button>
</div>
</div>
</b-col>
</b-row>
<BaseModal id="agreement-modal" size="lg">
<ServicesContent />
<PoliciesContent />
</BaseModal>
<BaseModal id="require-auth-modal">
<p>Please Login first...</p>
<b-button variant="primary" block @click="doSignIn">Login</b-button>
</BaseModal>
<BaseModal id="cancel-booking-modal">
<p class="modal-title my-2">Are you sure you want to cancel your booking</p>
<p>
If you cancel booking, your maid will be notified and will be released from this booking.
You will then be refunded according to our refund policy.
</p>
<b-button variant="danger" block class="py-2" @click="doCancelBooking"
>Cancel booking</b-button
>
</BaseModal>
</b-container>
</template>
<script>
import { createNamespacedHelpers } from 'vuex'
import { Vue, Component, Watch } from 'vue-property-decorator'
import ApiClient from '../../services'
import VSelect from '@alfsnd/vue-bootstrap-select'
import AddressSelect from '../../components/AddressSelect'
import MaidDetail from '../../components/MaidDetail'
import Constant from '../../config/booking'
import moment from 'moment'
import BaseModal from '../../components/Modal/BaseModal'
import ServicesContent from '../../components/Modal/ServicesContent'
import PoliciesContent from '../../components/Modal/PoliciesContent'
import CalendarDetail from '../../components/CalendarDetail'
import { isToday, getDisabledDateFromTimeSlot, getAvailableDateFromTimeSlot } from '../../utils'
const BookingStore = createNamespacedHelpers('booking')
const AuthStore = createNamespacedHelpers('auth')
const CalendarStore = createNamespacedHelpers('calendar')
@Component({
components: {
VSelect,
AddressSelect,
MaidDetail,
BaseModal,
ServicesContent,
PoliciesContent,
CalendarDetail
},
computed: {
...BookingStore.mapState(['oneTime', 'myBookingOneTime']),
...AuthStore.mapState(['isLoggedIn'])
},
methods: {
...BookingStore.mapMutations(['updateBookingOnetime', 'setBookingReview', 'setPaymentBody']),
...AuthStore.mapActions(['signIn']),
...CalendarStore.mapMutations(['resetMaidCalendar']),
...CalendarStore.mapActions(['updateMaidCalendarByMonth'])
}
})
export default class BookOneTime extends Vue {
options = {
accommodation: Constant.ACCOMMODATION,
services: Constant.SERVICES,
additional: Constant.ADDITIONAL_INFO,
time: Constant.TIME_OPTIONS
}
selectedTime = 'Start'
selectedDate = '' // ใช้้สำหรับแสดงรายละเอียดใต้ปฏิทิน
maidCalendarBookingTotalHours = []
disabledDates = []
availableTime = []
bookingForm = {
id: null, // for update booking only (unneccessary for new booking)
// type: null,
date: null,
time: null,
location: {
description: '',
main_text: '',
place_id: '',
secondary_text: ''
},
accommodation: null,
duration: null,
services: null,
additional: [],
method: 0, // 0 = Auto, 1 = Manual
maid: null,
description: ''
}
totalPrice = 0
deductPrice = 0
isAgreePolicy = false
isHistoryBooking = false
// ============ Calendar Attributes ============ //
get todayHighlight() {
return {
key: 'today',
// highlight: true,
content: 'blue',
dates: new Date()
}
}
get currentSelectDateHighlight() {
return {
key: 'selectedDay',
bar: true,
dates: this.bookingForm.date ? new Date(this.bookingForm.date) : null
}
}
get firstDot() {
return {
key: 'firstDot',
dot: {
class: 'custom-dot-gray'
},
dates: this.maidCalendarBookingTotalHours
.filter(d => d.total_hours >= 2)
.map(d => new Date(d.date))
}
}
get secondDot() {
return {
key: 'secondDot',
dot: {
class: 'custom-dot-gray'
},
dates: this.maidCalendarBookingTotalHours
.filter(d => d.total_hours >= 4)
.map(d => new Date(d.date))
}
}
get thirdDot() {
return {
key: 'thirdDot',
dot: {
class: 'custom-dot-gray'
},
dates: this.maidCalendarBookingTotalHours
.filter(d => d.total_hours >= 6)
.map(d => new Date(d.date))
}
}
get disabledDatesHighlight() {
return this.disabledDates.map(d => ({
key: 'busy',
highlight: 'gray',
content: 'gray',
dates: new Date(d.start_datetime)
}))
}
get maidCalendarAttrs() {
return [
this.todayHighlight,
this.currentSelectDateHighlight,
this.firstDot,
this.secondDot,
this.thirdDot,
...this.disabledDatesHighlight
]
}
get defaultCalendarAttrs() {
return [this.todayHighlight, this.currentSelectDateHighlight]
}
// ============ Calendar Attributes (end) ============ //
// ============ Options ============ //
get durationOptions() {
if (!this.bookingForm.time) return Constant.DURATION
return Constant.DURATION.filter(option => {
const endHour = this.bookingForm.time.hour + option.value
return (endHour <= 21 && this.bookingForm.time.minute === 0) || endHour < 21
})
}
get timeOptions() {
if (!this.bookingForm.date) return Constant.TIME_OPTIONS
if (!isToday(this.bookingForm.date)) return Constant.TIME_OPTIONS
const currentHour = moment().get('hour')
return Constant.TIME_OPTIONS.filter(t => t.hour > currentHour)
}
get grandTotal() {
return this.totalPrice - this.deductPrice
}
chooseMaid() {
if (this.bookingForm.date && this.bookingForm.duration) {
const end_datetime = moment(this.bookingForm.date)
.add(this.bookingForm.duration.value, 'hours')
.toISOString()
this.updateBookingOnetime(this.bookingForm)
this.$router.push({
path: '/select-maid',
query: { type: 'one-time', start_datetime: this.bookingForm.date, end_datetime }
})
} else {
this.updateBookingOnetime(this.bookingForm)
this.$router.push({ path: '/select-maid', query: { type: 'one-time' } })
}
}
bookNow() {
const end_datetime = moment(this.bookingForm.date)
.add(this.bookingForm.duration.value, 'hours')
.toISOString()
const calendar = [{ number: 1, start_datetime: this.bookingForm.date, end_datetime }]
this.setBookingReview({
...this.bookingForm,
amount: this.grandTotal,
type: 'one-time',
calendar
})
this.$router.push({ path: '/booking-review' })
}
onSelectedDate(day) {
const selectedDate = new Date(day.date)
const dateTimeString = selectedDate.toISOString()
this.selectedDate = dateTimeString
this.bookingForm.date = selectedDate.toISOString() // set date into booking form
this.bookingForm.time = null
}
get calendarValue() {
if (!this.bookingForm.date) return ``
return new Date(this.bookingForm.date).toDateString()
}
get recommendDurationText() {
if (this.bookingForm.accommodation === null) return ''
const recommendDuration = {
CONDO_1_BR: '3 - 4 hours',
CONDO_2_BR: '4 - 5 hours',
CONDO_3_BR: '5 - 6 hours',
CONDO_4_BR: '6 - 8 hours',
HOUSE_1_STORY: '3 - 4 hours',
HOUSE_2_STORIES: '4 - 6 hours',
HOUSE_3_STORIES: '6 - 8 hours',
OFFICE: '3 - 4 hours'
}
return `Recommend Duration ${recommendDuration[this.bookingForm.accommodation.value]}`
}
get shouldShowCalendarDetail() {
return !!this.selectedDate && this.bookingForm.method === 1 && !!this.bookingForm.maid
}
/**
* คืนค่า slot เวลาที่เมดว่าง ณ วันที่กำลังเลือกอยู่
*/
get maidCalendarAvailableTimes() {
if (!this.selectedDate) return []
return this.availableTime.filter(d => {
return moment(d.start_datetime).isSame(moment(this.selectedDate), 'date')
})
}
hasAvailableDateTimes(dateTimeString) {
if (!dateTimeString) return false
return this.availableTime.some(d => {
return moment(d.start_datetime).isSame(moment(dateTimeString), 'date')
})
}
get canBooking() {
return (
this.isAgreePolicy &&
this.isLoggedIn &&
!!this.bookingForm.location.main_text &&
this.isSelectAvailableDate
)
}
get isSelectAvailableDate() {
if (this.bookingForm.method === 0) return true
if (!this.bookingForm.date) return false
const startDatetime = moment(this.bookingForm.date)
const endDateTime = moment(this.bookingForm.date).add(this.bookingForm.duration.value, 'hours')
return this.availableTime.some(d => {
const maidStartDateTime = moment(d.start_datetime)
const maidEndDateTime = moment(d.end_datetime)
return startDatetime >= maidStartDateTime && endDateTime <= maidEndDateTime
})
}
get currentRoute() {
return this.$router.currentRoute
}
get isBookingOneTime() {
return this.currentRoute.name === 'BookingOneTime'
}
@Watch('bookingForm.time')
onTimeChanged(time) {
const { hour, minute } = time
const startDate = moment(this.bookingForm.date)
.hour(hour)
.minute(minute)
.second(0)
.millisecond(0)
this.bookingForm.date = startDate.toISOString()
}
@Watch('bookingForm.duration')
async calcuatePrice(duration) {
const result = await ApiClient.getPriceOneTime(duration.value)
this.totalPrice = result.data.price
}
async getDeductPrice(hr) {
const result = await ApiClient.getPriceOneTime(hr)
this.deductPrice = result.data.price
}
@Watch('bookingForm.method')
onMethodChanged(newVal) {
this.updateBookingOnetime(this.bookingForm)
}
async mounted() {
if (!this.isLoggedIn) {
this.$root.$emit('bv::show::modal', 'require-auth-modal')
}
this.resetMaidCalendar()
if (this.isBookingOneTime) {
Object.assign(this.bookingForm, this.oneTime)
const { date } = this.bookingForm
date && (this.calendarValue = new Date(date).toDateString())
} else {
await this.fetchBookingDetail()
}
}
@Watch('bookingForm.maid')
onMaidChanged(newVal) {
const now = new Date()
this.getMaidBookingTotal({ id: newVal.id, month: now.getMonth() + 1, year: now.getFullYear() })
}
@Watch('isLoggedIn')
onIsLoggedInChanged() {
if (this.isLoggedIn) {
this.$root.$emit('bv::hide::modal', 'require-auth-modal')
} else {
this.$root.$emit('bv::show::modal', 'require-auth-modal')
}
}
async onMonthChanged(page) {
if (this.bookingForm.maid) {
await this.getMaidBookingTotal({ id: this.bookingForm.maid.id, ...page })
}
}
async getMaidBookingTotal(payload) {
const result = await Promise.all([
ApiClient.maidCalendarAllMonth(payload),
ApiClient.maidCalendarBookingTotalHours(payload)
])
if (result) {
this.updateMaidCalendarByMonth({
year: payload.year,
month: payload.month,
calendar: result[0].data,
id: this.bookingForm.maid.id
})
this.disabledDates = getDisabledDateFromTimeSlot(result[0].data)
this.availableTime = getAvailableDateFromTimeSlot(result[0].data)
this.maidCalendarBookingTotalHours = result[1].data
}
}
openServiceModal() {
this.isAgreePolicy = !this.isAgreePolicy // prevent check by clicling at the text
this.$root.$emit('bv::show::modal', 'agreement-modal')
}
doSignIn() {
this.$root.$emit('bv::hide::modal', 'require-auth-modal')
this.signIn()
}
// Booking detail
get bookingID() {
return this.myBookingOneTime.booking.id
}
async fetchBookingDetail() {
try {
console.log('this.myBookingOneTime.booking.id', this.myBookingOneTime.booking.id)
console.log('$route', this.$route.params)
const bookingId = parseInt(this.$route.params.id)
const result = await ApiClient.bookingDetail(bookingId)
if (result.data) {
const bookingDetail = result.data
const startDate = new Date(bookingDetail.booking_calendars[0].start_datetime)
// find matched options from contant
const accommodation = Constant.ACCOMMODATION.find(
option => option.value === bookingDetail.accommodation
)
const services = Constant.SERVICES.find(
option => option.value === bookingDetail.services[0]
)
const duration = Constant.DURATION.find(option => option.value === bookingDetail.duration)
const time = Constant.TIME_OPTIONS.find(
option => option.hour === startDate.getHours() && option.minute === startDate.getMinutes()
)
this.bookingForm = {
accommodation,
services,
duration,
maid: bookingDetail.maid,
date: startDate.toISOString(),
additional: bookingDetail.additional_infos,
time,
id: bookingDetail.id,
location: {
main_text: bookingDetail.location_name,
description: bookingDetail.location_secondary,
secondary_text: bookingDetail.location_secondary,
latitude: bookingDetail.location_latitude,
longitude: bookingDetail.location_longitude
},
payment: bookingDetail.payment,
description: bookingDetail.description
}
this.setPaymentBody({
refno: bookingDetail.payment.ref_no,
merchantid: bookingDetail.payment.merchant_id,
customeremail: bookingDetail.payment.customer_email,
productdetail: bookingDetail.payment.product_detail,
total: bookingDetail.payment.total_price
})
// hide update button if this booking already passed
this.isHistoryBooking = Date.now() > startDate
await this.fetchMaidDetail(bookingDetail.maid.id)
// get deduct price
await this.getDeductPrice(bookingDetail.duration)
}
} catch (e) {
console.log(e)
}
}
async fetchMaidDetail(maidID) {
try {
const result = await ApiClient.maidDetail(maidID)
this.bookingForm.maid = result.data
} catch (e) {
console.log(e)
}
}
toggleCancelModal() {
this.$root.$emit('bv::show::modal', 'cancel-booking-modal')
}
async doUpdateBooking() {
const start_datetime = this.bookingForm.date
const end_datetime = moment(this.bookingForm.date)
.add(this.bookingForm.duration.value, 'hours')
.toISOString()
const calendar = [{ number: 1, start_datetime, end_datetime }]
this.setBookingReview({
...this.bookingForm,
amount: this.grandTotal,
type: 'one-time',
calendar,
isUpdateBooking: true
})
this.$router.push({ path: '/booking-review' })
}
async doCancelBooking() {
try {
await ApiClient.cancelBooking(this.myBookingOneTime.booking.id)
this.$router.push({
name: 'MyBooking',
query: {
tab: 'history'
}
})
} catch (e) {
console.log(e.response)
} finally {
this.$root.$emit('bv::hide::modal', 'base-modal')
}
}
onClose() {
this.$router.push({
name: 'MyBooking',
query: {
tab: this.isHistoryBooking ? 'history' : 'upcoming'
}
})
}
}
</script>
<style lang="scss" scoped>
.modal-title {
color: black;
font-weight: bold;
}
.btn-close {
background: transparent;
border: none;
outline: none;
vertical-align: middle;
color: #757575;
&:hover {
color: black;
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment