FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
ameliabooking
/
assets
/
views
/
backend
/
calendar
Edit File: Calendar.vue
<template> <div class="am-wrap"> <div id="am-calendar" class="am-body"> <get-premium-banner></get-premium-banner> <!-- Page Header --> <page-header :categories="filterCategoriesByProviderServiceIDs(options.entities.categories)" :locations="options.entities.locations" :params="params" :fetched="appointmentsFetched && eventsFetched" @newAppointmentBtnClicked="showDialogNewAppointment()" @changeFilter="filterData" @selectAllInCategory="selectAllInCategory" > </page-header> <div class="am-section am-calendar"> <!-- Employees Filter --> <div v-if="$root.settings.capabilities.canReadOthers === true" class="am-calendar-employees"> <div class="am-calendar-employee" :class="{ active: params.providers.length === 0 && employeesPreselectedCount === 0 }" @click="filterAllEmployees" v-if="!$root.licence.isLite" > <div class="am-profile-photo"> <div class="am-all">{{ $root.labels.all }}</div> </div> <p>{{ $root.labels.all_employees }}</p> </div> <div v-for="employee in options.entities.employees" :key="employee.id" class="am-calendar-employee" :class="{ active: params.providers.indexOf(employee.id) !== -1 }" @click="filterEmployees(employee)" > <div class="am-profile-photo"> <img :src="pictureLoad(employee, true)" @error="imageLoadError(employee, true)"> </div> <p class="am-calendar-employee-name">{{ employee.firstName + ' ' + employee.lastName }}</p> </div> </div> <!-- Calendar --> <div class="am-calendar-scroll"> <v-date-picker :locale="$root.locale" @input="selectDay" v-model="selectedDate" mode="single" id="am-calendar-picker" class='am-calendar-picker' tint-color='#1A84EE' :show-day-popover=false :is-expanded=false :is-inline=true :is-required=true :formats="vCalendarFormats" > </v-date-picker> <full-calendar :class="{'am-licence-calendar': notInLicence('starter')}" ref="calendar" :events="events" :config="config" @event-selected="eventSelected" @event-render="eventRender" @event-drop="eventDrop" @event-drag-start="eventDragStart" @event-drag-stop="eventDragStop" > </full-calendar> <!-- Content Spinner --> <div class="am-spinner am-section" v-show="!appointmentsFetched || !eventsFetched || !appointmentsFetchedFiltered || !eventsFetchedFiltered"> <img :src="$root.getUrl + 'public/img/spinner.svg'"/> </div> </div> </div> <!-- Button New --> <div v-if="$root.settings.role !== 'customer' && canWriteAppointments()" id="am-button-new" class="am-button-new"> <el-button id="am-plus-symbol" type="primary" icon="el-icon-plus" @click="showDialogNewAppointment()"></el-button> </div> <!-- Dialog Event --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-event" :show-close="false" :visible.sync="dialogEvent" v-if="dialogEvent" > <dialog-event :event="event" :employees="options.entities.employees" :locations="options.entities.locations" :tags="options.entities.tags" @closeDialog="closeDialogEvent" @saveCallback="saveCallback" @duplicateCallback="duplicateEventCallback" > </dialog-event> </el-dialog> </transition> <!-- Dialog New Appointment --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog" :visible.sync="dialogAppointment" :show-close="false" v-if="dialogAppointment" > <dialog-appointment :appointment="appointment" :recurringAppointments="recurringAppointments" :savedAppointment="savedAppointment" :bookings="bookings" :options="options" :customerCreatedCount="customerCreatedCount" :packageCustomer="null" :current-user="currentUser" :customersNoShowCount="customersNoShowCount" @sortBookings="sortBookings" @saveCallback="saveCallback" @duplicateCallback="duplicateAppointmentCallback" @closeDialog="closeDialogAppointment" @showDialogNewCustomer="showDialogNewCustomer" @editPayment="editPayment" @openRecurringAppointment="openRecurringAppointment" > </dialog-appointment> </el-dialog> </transition> <!-- Dialog New Customer --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog" size="full" :visible.sync="dialogCustomer" :show-close="false" v-if="dialogCustomer"> <dialog-customer :customer="customer" @saveCallback="saveCustomerCallback" @closeDialog="dialogCustomer = false" ></dialog-customer> </el-dialog> </transition> <!-- Dialog Payment --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-coupon" :visible.sync="dialogPayment" :show-close="false" v-if="dialogPayment" > <dialog-payment :modalData="selectedPaymentModalData" :appointmentFetched=true :bookingFetched=true @closeDialogPayment="dialogPayment = false" @updatePaymentCallback="updatePaymentCallback" @deletePaymentCallback="deletePaymentCallback" > </dialog-payment> </el-dialog> </transition> <!-- Help Button --> <el-col :md="6" class=""> <a class="am-help-button" href="https://wpamelia.com/admin-calendar/" target="_blank" rel="nofollow"> <i class="el-icon-question"></i> {{ $root.labels.need_help }}? </a> </el-col> <!-- <dialog-new-customize></dialog-new-customize>--> </div> </div> </template> <script> import 'fullcalendar-scheduler' import licenceMixin from '../../../js/common/mixins/licenceMixin' import appointmentMixin from '../../../js/backend/mixins/appointmentMixin' import backendEventMixin from '../../../js/backend/mixins/eventMixin' import commonEventMixin from '../../../js/common/mixins/eventMixin' import customerMixin from '../../../js/backend/mixins/customerMixin' import dateMixin from '../../../js/common/mixins/dateMixin' import DialogAppointment from '../appointments/DialogAppointment.vue' import DialogCustomer from '../customers/DialogCustomer.vue' import DialogEvent from '../events/DialogEvent.vue' import DialogPayment from '../finance/DialogFinancePayment.vue' import durationMixin from '../../../js/common/mixins/durationMixin' import entitiesMixin from '../../../js/common/mixins/entitiesMixin' import Form from 'form-object' import imageMixin from '../../../js/common/mixins/imageMixin' import moment from 'moment' import notifyMixin from '../../../js/backend/mixins/notifyMixin' import PageHeader from '../parts/PageHeader.vue' import paymentMixin from '../../../js/backend/mixins/paymentMixin' import { FullCalendar } from 'vue-full-calendar' // import DialogNewCustomize from '../parts/DialogNewCustomize.vue' import GetPremiumBanner from '../parts/GetPremiumBanner.vue' export default { mixins: [ licenceMixin, appointmentMixin, backendEventMixin, commonEventMixin, customerMixin, dateMixin, durationMixin, entitiesMixin, imageMixin, notifyMixin, paymentMixin ], data () { return { customersNoShowCount: [], currentUser: null, employeesPreselectedCount: 0, appointmentsFetchCounter: 0, eventsFetchCounter: 0, occupiedSlots: [], initCall: true, globalBusinessHours: [], employeesResources: [], customerServices: [], customerEmployees: [], parsedEmployees: null, previousViewName: null, customer: null, resources: [], dialogEvent: false, dialogAppointment: false, dialogPayment: false, events: [], appointmentsFetched: false, eventsFetched: false, appointmentsFetchedFiltered: false, eventsFetchedFiltered: false, form: new Form(), minTimeInSeconds: null, maxTimeInSeconds: null, params: { dates: [], locations: [], providers: [], services: [], source: 'calendar' }, selectedDate: moment().toDate(), selectedPaymentModalData: null } }, created () { Form.defaults.axios = this.$http this.config.slotLabelFormat = this.config.timeFormat = this.config.views.timelineDay.slotLabelFormat = this.momentTimeFormat this.config.listDayAltFormat = this.momentDateFormat this.config.views.week.columnFormat = 'ddd ' + this.momentDateFormat .replace(/Y/g, '').replace(/y/g, '').replace(/^([^a-zA-Z0-9])*/g, '').replace(/([^a-zA-Z0-9])*$/g, '') }, mounted () { this.employeesPreselectedCount = this.$root.settings.general.calendarEmployeesPreselected this.setInitialCustomers() this.getCalendarOptions(true) let view = this.$refs.calendar.fireMethod('getView') this.params.dates = [view.start.format('YYYY-MM-DD'), view.end.clone().subtract(1, 'days').format('YYYY-MM-DD')] this.initVCalendar() }, methods: { duplicateEventCallback (entity) { this.event = entity this.event.id = 0 this.event.duplicated = true this.event.periods.forEach((period) => { period.googleCalendarEventId = null period.googleMeetUrl = null period.outlookCalendarEventId = null period.microsoftTeamsUrl = null period.appleCalendarEventId = null }) setTimeout(() => { this.dialogEvent = true }, 300) }, saveCallback () { this.getCalendarOptions(false) }, showDialogNewCustomer () { this.customer = this.getInitCustomerObject() this.dialogCustomer = true }, getCalendarOptions (initLoad) { this.options.fetched = false let $this = this this.fetchEntities(function (success) { if (success) { let tags = [] $this.options.entities.tags.forEach(function (eventTag) { if ($this.options.entities.tags.indexOf(eventTag.name) === -1) { tags.push(eventTag.name) } }) $this.options.entities.tags = tags $this.setBookings(0) if (initLoad) { $this.params.providers = $this.employeesPreselectedCount > 0 ? $this.options.entities.employees .map(employee => employee.id) .slice(0, $this.employeesPreselectedCount) : [] } if (!initLoad || $this.employeesPreselectedCount > -1) { $this.getCalendar() } else { $this.appointmentsFetched = true $this.appointmentsFetchedFiltered = true $this.eventsFetched = true $this.eventsFetchedFiltered = true } } $this.fetched = true $this.options.fetched = true }, { types: ['locations', 'employees', 'categories', 'custom_fields', 'tags', 'packages', 'resources', 'coupons'], page: 'calendar', isFrontEnd: false, isPanel: false, dates: $this.params.dates }) }, getEmployeeDaysOffDates (dayOffList) { let dayOffDates = [] dayOffList.forEach(function (daysOff) { let dayOffStartDate = daysOff.repeat ? moment(moment().format('YYYY') + '-' + moment(daysOff.startDate, 'YYYY-MM-DD').format('MM-DD'), 'YYYY-MM-DD') : moment(daysOff.startDate, 'YYYY-MM-DD') let dayOffEndDate = daysOff.repeat ? moment(moment().format('YYYY') + '-' + moment(daysOff.endDate, 'YYYY-MM-DD').format('MM-DD'), 'YYYY-MM-DD') : moment(daysOff.endDate, 'YYYY-MM-DD') while (dayOffStartDate.isSameOrBefore(dayOffEndDate)) { dayOffDates.push(dayOffStartDate.format('YYYY-MM-DD')) dayOffStartDate.add(1, 'days') } }) return dayOffDates }, getParsedEmployees () { let minAvailableDate = moment(this.$root.settings.slotDateConstraints.minDate, 'YYYY-MM-DD') let maxAvailableDate = moment(this.$root.settings.slotDateConstraints.maxDate, 'YYYY-MM-DD') let employees = JSON.parse(JSON.stringify(this.options.entities.employees)) let view = this.$refs.calendar.fireMethod('getView') let startDate = view.start.clone() let endDate = view.end.clone().subtract(1, 'days') let paramsDates = [] let unavailableDayDates = [] // get range dates while (startDate.isSameOrBefore(endDate)) { paramsDates.push(startDate.format('YYYY-MM-DD')) if ((startDate.isBefore(minAvailableDate) || startDate.isSameOrAfter(maxAvailableDate)) && this.$root.settings.role === 'customer') { unavailableDayDates.push(startDate.format('YYYY-MM-DD')) } startDate.add(1, 'days') } unavailableDayDates = _.intersection(unavailableDayDates, paramsDates) let unavailableDateIndexes = paramsDates.filter(value => unavailableDayDates.indexOf(value) !== -1).map(date => moment(date).day()) let that = this // alter employee period list for date range employees.forEach(function (employee) { // remove days off if they are out or params range let dayOffDates = _.intersection(that.getEmployeeDaysOffDates(employee.dayOffList), paramsDates) let dayOffIndexes = paramsDates.filter(value => dayOffDates.indexOf(value) !== -1).map(date => moment(date).day()) employee.specialDayList.forEach(function (specialDays) { let specialDaysDates = [] let startDate = moment(specialDays.startDate, 'YYYY-MM-DD') while (startDate.isSameOrBefore(moment(specialDays.endDate, 'YYYY-MM-DD'))) { specialDaysDates.push(startDate.format('YYYY-MM-DD')) startDate.add(1, 'days') } // remove special days if they are out or params range specialDaysDates = _.intersection(specialDaysDates, paramsDates) // remove special days if they are in days off range specialDaysDates = specialDaysDates.filter(specialDay => dayOffDates.indexOf(specialDay) === -1) let specialDayIndexes = paramsDates.filter(value => specialDaysDates.indexOf(value) !== -1).map(date => moment(date).day()) let weekDayIndexes = employee.weekDayList.map(day => day.dayIndex === 7 ? 0 : day.dayIndex) let parsedDayIndexes = [] // set working hours based on special day if employee works on that day employee.weekDayList.forEach(function (day) { let dayIndex = day.dayIndex === 7 ? 0 : day.dayIndex if (specialDayIndexes.indexOf(dayIndex) !== -1) { day.regularEndTime = day.endTime day.regularStartTime = day.startTime day.endTime = specialDays.periodList[specialDays.periodList.length - 1].endTime day.startTime = specialDays.periodList[0].startTime day.periodList = specialDays.periodList day.timeOutList = [] parsedDayIndexes.push(dayIndex) } }) // set working hours based on special day if employee doesn't works on that day specialDayIndexes.forEach(function (specialDayIndex) { if (weekDayIndexes.indexOf(specialDayIndex) === -1 && parsedDayIndexes.indexOf(specialDayIndex) === -1) { employee.weekDayList.push({ id: null, dayIndex: specialDayIndex === 0 ? 7 : specialDayIndex, startTime: specialDays.periodList[0].startTime, endTime: specialDays.periodList[specialDays.periodList.length - 1].endTime, periodList: specialDays.periodList, timeOutList: [] }) } }) }) // remove employee week days that are not in params dates range or are in days off range let dayIndexesToRemove = [] employee.weekDayList.forEach(function (day, index) { if (paramsDates.map(date => moment(date).day()).indexOf(day.dayIndex === 7 ? 0 : day.dayIndex) === -1 || dayOffIndexes.indexOf(day.dayIndex === 7 ? 0 : day.dayIndex) !== -1 || unavailableDateIndexes.indexOf(day.dayIndex === 7 ? 0 : day.dayIndex) !== -1) { dayIndexesToRemove.push(index) } }) for (let index = dayIndexesToRemove.length - 1; index >= 0; index--) { employee.weekDayList.splice(dayIndexesToRemove[index], 1) } }) return employees }, setMinMaxTime (that, start, end) { let startParts = start.substring(0, 16).split(' ') let endParts = end.substring(0, 16).split(' ') if (startParts[0] === endParts[0]) { let minTimeInSeconds = that.getStringTimeInSeconds(startParts[1]) if (that.minTimeInSeconds === null || minTimeInSeconds < that.minTimeInSeconds) { that.minTimeInSeconds = minTimeInSeconds } let maxTimeInSeconds = that.getStringTimeInSeconds(endParts[1]) if (that.maxTimeInSeconds === null || maxTimeInSeconds > that.maxTimeInSeconds) { that.maxTimeInSeconds = maxTimeInSeconds } } else { that.minTimeInSeconds = 0 that.maxTimeInSeconds = 86400 } }, reFetchResources () { if (this.appointmentsFetchedFiltered && this.eventsFetchedFiltered) { this.$refs.calendar.fireMethod('refetchResources') } }, getEvents (counter) { let providers = this.params.providers.length === 0 ? this.options.entities.employees.map(employee => employee.id) : this.params.providers if (this.$root.settings.role === 'provider' && this.$root.settings.roles.allowWriteEvents) { providers = this.options.entities.employees.map(employee => employee.id) } // fix for events that spans through 2 weeks (not shown in 2nd week, Week and List view) let view = this.$refs.calendar.fireMethod('getView') let params = JSON.parse(JSON.stringify(this.params)) if (view.type === 'agendaWeek' || view.type === 'listWeek') { params.dates[0] = moment(params.dates[0]).subtract(14, 'days').format('YYYY-MM-DD') } let dates = params.dates[0] === params.dates[1] ? [params.dates[0]] : params.dates this.$http.get(`${this.$root.getAjaxUrl}/events`, { params: this.getAppropriateUrlParams( Object.assign(JSON.parse(JSON.stringify(params)), {providers: providers, status: 'approved', dates: dates}) ) }) .then(response => { if (counter !== this.eventsFetchCounter) { return } let that = this let employeesStyle = that.$refs.calendar.fireMethod('getView').name === 'listWeek' ? 'style="display: inline; margin-left: 10px;"' : '' let paramsStart = moment(this.params.dates[0], 'YYYY-MM-DD') let paramsEnd = moment(this.params.dates[1] + ' 23:59:59', 'YYYY-MM-DD HH:mm:ss') response.data.data.events.forEach(function (event) { let employeesInfo = '' event.providers.forEach(function (employee) { employeesInfo += '<div ' + employeesStyle + '><img src="' + that.pictureLoad(employee, true) + '">' + employee.firstName.replace(/<\/?[^>]+(>|$)/g, '') + ' ' + employee.lastName.replace(/<\/?[^>]+(>|$)/g, '') + '</div>' }) that.getExplodedPeriods(that.getImplodedPeriods(event.periods)).forEach(function (period) { event.providers.forEach(function (provider, index) { if (that.$refs.calendar.fireMethod('getView').name === 'timelineDay' || that.$refs.calendar.fireMethod('getView').name === 'day' || index === 0) { let locationString = '' if (event.locationId) { let location = that.getLocationById(event.locationId) locationString = location.address ? location.address : location.name } let periodStart = moment(period.periodStart, 'YYYY-MM-DD HH:mm:ss') let periodEnd = moment(period.periodEnd, 'YYYY-MM-DD HH:mm:ss') if (dates.length !== 1 && !(periodStart.isBetween(paramsStart, paramsEnd) || periodEnd.isBetween(paramsStart, paramsEnd))) { return } that.setMinMaxTime(that, period.periodStart, period.periodEnd) that.events.push({ id: event.id, title: event.name.replace(/<\/?[^>]+(>|$)/g, ''), bookings: event.bookings, start: periodStart.format(), end: periodEnd.format(), color: that.shadeColor(event.color, 0.7), borderColor: event.color, customer: that.$root.labels.event, employeesInfo: '<div style="display: inline-block;">' + employeesInfo + '</div>', location: locationString.replace(/<\/?[^>]+(>|$)/g, '') || (event.customLocation && event.customLocation.replace(/<\/?[^>]+(>|$)/g, '')), status: event.status, eventOverlap: false, editable: that.canWriteEvents(), resourceId: provider.id.toString(), type: 'event', icon: '<img style="float: right;" src="' + that.$root.getUrl + 'public/img/event.svg' + '">' }) that.resources.push({ id: provider.id.toString(), title: provider.firstName.replace(/<\/?[^>]+(>|$)/g, '') + ' ' + provider.lastName.replace(/<\/?[^>]+(>|$)/g, '') }) } }) }) }) if (this.appointmentsFetched) { this.setHours() } this.eventsFetched = true this.eventsFetchedFiltered = true this.reFetchResources() }) .catch(e => { console.log(e.message) this.eventsFetched = true this.eventsFetchedFiltered = true }) }, getAppointments (counter) { if (this.$root.settings.role === 'provider' && this.options.entities.employees.length === 1 && this.options.entities.employees[0].timeZone) { this.params.timeZone = this.options.entities.employees[0].timeZone } this.$http.get(`${this.$root.getAjaxUrl}/appointments`, { params: this.getAppropriateUrlParams(this.params) }) .then(response => { if (counter !== this.appointmentsFetchCounter) { return } this.currentUser = response.data.data.currentUser if (this.$root.settings.role !== 'admin') { this.setMissingServices(response.data.data.appointments) } let that = this let appointmentDays = {} if (this.$root.settings.role === 'customer') { this.useSortedDateStrings(Object.keys(response.data.data.appointments)).forEach(function (dateKey) { response.data.data.appointments[dateKey].appointments.forEach(function (appointment) { appointment.bookings.forEach(function (booking) { let parsedAppointment = JSON.parse(JSON.stringify(appointment)) parsedAppointment.bookings = [ booking ] if (!(dateKey in appointmentDays)) { appointmentDays[dateKey] = response.data.data.appointments[dateKey] appointmentDays[dateKey].appointments = [] } appointmentDays[dateKey].appointments.push(parsedAppointment) }) }) }) } else { let customersIds = this.options.entities.customers.map(customer => parseInt(customer.id)) let customers = this.options.entities.customers this.useSortedDateStrings(Object.keys(response.data.data.appointments)).forEach(function (dateKey) { response.data.data.appointments[dateKey].appointments.forEach(function (app) { app.checked = false app.bookings.forEach((booking) => { if (customersIds.indexOf(parseInt(booking.customer.id)) === -1) { customersIds.push(booking.customer.id) customers.push(booking.customer) } }) }) }) this.options.entities.customers = Object.values(customers.sort((a, b) => (a.firstName.toLowerCase() > b.firstName.toLowerCase()) ? 1 : -1)) appointmentDays = response.data.data.appointments } this.useSortedDateStrings(Object.keys(appointmentDays)).forEach(function (appointmentDay) { for (let i = 0; i < appointmentDays[appointmentDay].appointments.length; i++) { // Do not render appointments with rejected and canceled status if (['rejected', 'canceled', 'no-show'].indexOf(appointmentDays[appointmentDay].appointments[i].status) === -1) { // If customer is logged in don't render appointments where his customer booking has status rejected or canceled if (that.$root.settings.role !== 'customer' || (that.$root.settings.role === 'customer' && ['rejected', 'canceled', 'no-show'].indexOf(appointmentDays[appointmentDay].appointments[i].bookings[0].status) === -1)) { let appointment = appointmentDays[appointmentDay].appointments[i] let service = that.options.entities.employees.find(employee => employee.id === appointment.providerId).serviceList.find(service => service.id === appointment.serviceId) let customer = that.getCustomerInfo(appointment.bookings[0]) let employee = that.getProviderById(appointment.providerId) let location = that.getLocationById(appointment.locationId ? appointment.locationId : employee.locationId) if (typeof service === 'undefined') { service = that.getServiceById(appointment.serviceId) } if (that.$root.settings.role === 'customer') { if (that.customerServices.indexOf(appointment.serviceId) === -1) { that.customerServices.push(appointment.serviceId) } if (that.customerEmployees.indexOf(appointment.providerId) === -1) { that.customerEmployees.push(appointment.providerId) } } that.setMinMaxTime(that, appointment.bookingStart, appointment.bookingEnd) that.events.push({ id: appointment.id, bookings: appointment.bookings, serviceId: service.id, timeBefore: service.timeBefore === null ? 0 : service.timeBefore, timeAfter: service.timeAfter === null ? 0 : service.timeAfter, title: service.name.replace(/<\/?[^>]+(>|$)/g, ''), start: moment(appointment.bookingStart, 'YYYY-MM-DD HH:mm').format(), end: moment(appointment.bookingEnd, 'YYYY-MM-DD HH:mm').format(), color: that.shadeColor(service.color, 0.7), borderColor: service.color, customer: appointment.bookings.length > 1 ? that.$root.labels.group_booking : customer.firstName.replace(/<\/?[^>]+(>|$)/g, '') + ' ' + customer.lastName.replace(/<\/?[^>]+(>|$)/g, ''), employeeId: employee.id, employeesInfo: '<div style="display: inline-block;"><div><img src="' + that.pictureLoad(employee, true) + '">' + employee.firstName.replace(/<\/?[^>]+(>|$)/g, '') + ' ' + employee.lastName.replace(/<\/?[^>]+(>|$)/g, '') + '</div></div>', location: location ? (location.address ? location.address.replace(/<\/?[^>]+(>|$)/g, '') : location.name.replace(/<\/?[^>]+(>|$)/g, '')) : '', status: appointment.status, eventOverlap: false, resourceId: employee.id.toString(), editable: that.canWriteAppointments() && (that.$root.settings.role === 'customer' ? !appointment.past && appointment.cancelable && appointment.cancelable : true), past: appointment.past, cancelable: appointment.cancelable, type: 'appointment', icon: '' }) } } } }) if (this.eventsFetched) { this.setHours() } if (this.$root.settings.role === 'customer') { that.occupiedSlots = response.data.data.occupied for (let date in that.occupiedSlots) { that.occupiedSlots[date].forEach(function (appointment) { that.createBackgroundEvent(!(that.customerEmployees.length === 1), date, appointment.startTime, appointment.endTime, appointment.employeeId, []) }) } this.createEmployeesBackgroundEvents(this.parsedEmployees) } this.appointmentsFetched = true this.appointmentsFetchedFiltered = true this.customersNoShowCount = response.data.data.customersNoShowCount this.reFetchResources() }) .catch(e => { this.appointmentsFetched = true this.appointmentsFetchedFiltered = true }) }, setHours () { this.parsedEmployees = this.$root.settings.role !== 'customer' ? this.getParsedEmployees() : this.getParsedEmployees().filter(employee => this.customerEmployees.indexOf(employee.id) !== -1) this.createResourcesForScheduler(this.parsedEmployees) this.setGlobalBusinessHours(this.parsedEmployees) }, getCalendar () { this.events = [] this.minTimeInSeconds = null this.maxTimeInSeconds = null this.appointmentsFetched = false this.eventsFetched = false this.appointmentsFetchedFiltered = false this.eventsFetchedFiltered = false Object.keys(this.params).forEach((key) => (!this.params[key] && this.params[key] !== 0) && delete this.params[key]) this.resources = [] this.selectedDate = moment(this.params.dates[0], 'YYYY-MM-DD').toDate() this.appointmentsFetchCounter++ this.eventsFetchCounter++ this.getAppointments(this.appointmentsFetchCounter) this.getEvents(this.eventsFetchCounter) }, callViewRender (view) { let that = this if (that.params.dates.length > 0) { let start = view.start.format('YYYY-MM-DD') let end = view.end.clone().subtract(1, 'days').format('YYYY-MM-DD') if ( (!moment(start).isBetween(this.params.dates[0], this.params.dates[1], null, '[]') || !moment(end).isBetween(this.params.dates[0], this.params.dates[1], null, '[]')) || this.previousViewName !== view.name ) { this.previousViewName = view.name this.params.dates = [start, end] if (!this.initCall) { this.filterData() } this.initCall = false } } }, filterData () { this.getCalendarOptions(false) }, eventSelected (event) { if (event.type === 'appointment') { if (!this.canWriteAppointments(event.employeeId) || !event.editable) { return } this.showDialogEditAppointment(event.id) } if (event.type === 'event') { if (!this.canWriteEvents() || !event.editable) { return } this.showDialogEditEvent(event.id) } }, eventRender (event, element, view) { if (view.name !== 'timelineDay' && event.type !== 'fake') { element.find('.fc-content').prepend('<span class="am-calendar-customer">' + event.customer + event.icon + '</span>') element.find('.fc-title').prepend(`<span class="am-calendar-status ${event.status}"></span>`) element.find('.fc-content').append('<div class="flexed-between"><span class="am-calendar-employee">' + event.employeesInfo + '</span></div>') } if (view.name !== 'listWeek' && event.type !== 'fake') { let tooltip = '<div class="am-tooltip el-tooltip__popper is-light">' + '<span class="am-tooltip-color" style="background-color:' + event.borderColor + ';"></span> ' + '<span class="am-calendar-status ' + event.status + '"></span>' + '<h4>' + event.customer + '</h4>' + '<p>' + event.start.format(this.momentTimeFormat) + ' - ' + event.end.format(this.momentTimeFormat) + '</p>' + '<p>' + event.title + '</p>' let button = !event.editable ? '' : '<button type="button" class="el-button el-button--default"><span>' + this.$root.labels.edit + '</span></button>' tooltip += (typeof event.location !== 'undefined' && event.location && event.location !== '') ? '<p icon="el-icon-location" class="am-tooltip-address">' + event.location + '</p>' : '' tooltip += '<div class="flexed-between"><h4>' + event.employeesInfo + '</h4>' + button + '</div>' + '</div>' element.append(tooltip) } if (view.name === 'listWeek') { let serviceTime = element.find('.fc-list-item-time').html() let serviceName = element.find('.fc-list-item-title').html() element.find('.fc-list-item-time').html(serviceTime + '<span class="am-calendar-list-customer-name">' + event.customer + '</span>') element.find('.fc-list-item-title').html(serviceName + '<span class="am-calendar-employee">' + event.employeesInfo + '</span><span class="am-calendar-status ' + event.status + '"></span>') } }, eventDrop (event, delta, revertFunc) { if (event.type === 'event') { revertFunc() return } let draggedEvent = event let draggedDateString = draggedEvent.start.format('YYYY-MM-DD') let employee = JSON.parse(JSON.stringify(this.getProviderById(draggedEvent.employeeId))) let dayIndex = draggedEvent.start.isoWeekday() let workingHoursForDayIndex = employee.weekDayList.find(day => day.dayIndex === dayIndex) // set event start and event end taking into account service buffer time after and service buffer time before let realDraggedEventStart = draggedEvent.start.clone().subtract(draggedEvent.timeBefore, 'seconds') let realDraggedEventEnd = draggedEvent.end.clone().add(draggedEvent.timeAfter, 'seconds') let realStartTime = realDraggedEventStart.format('HH:mm:ss') let realEndTime = realDraggedEventEnd.format('HH:mm:ss') === '00:00:00' ? '24:00:00' : realDraggedEventEnd.format('HH:mm:ss') let realStartDate = realDraggedEventStart.format('YYYY-MM-DD HH:mm') let realEndDate = realDraggedEventEnd.format('YYYY-MM-DD HH:mm') // Check if event is dropped in past let droppedInPast = moment().diff(moment(realStartDate), 'seconds') >= 0 // Check if event is dropped in the employee's special day employee.specialDayList.forEach(function (specialDays) { if ( draggedEvent.start.isSameOrAfter(moment(specialDays.startDate + ' 00:00:00', 'YYYY-MM-DD HH:ii:ss')) && draggedEvent.start.isSameOrBefore(moment(specialDays.endDate + ' 24:00:00', 'YYYY-MM-DD HH:ii:ss')) ) { let specialDaysDates = [] let startDate = moment(specialDays.startDate, 'YYYY-MM-DD') while (startDate.isSameOrBefore(moment(specialDays.endDate, 'YYYY-MM-DD'))) { specialDaysDates.push(startDate.format('YYYY-MM-DD')) startDate.add(1, 'days') } if (specialDaysDates.indexOf(draggedDateString) !== -1 && specialDays.periodList.length) { workingHoursForDayIndex = { startTime: specialDays.periodList[0].startTime, endTime: specialDays.periodList[0].endTime, periodList: specialDays.periodList, timeOutList: [], dayIndex: dayIndex } } } }) // Check if event is dropped in the employee's working hours let droppedInWorkingHours = typeof workingHoursForDayIndex !== 'undefined' && ( moment(realStartTime, 'HH:mm:ss').isBetween(moment(workingHoursForDayIndex.startTime, 'HH:mm:ss'), moment(workingHoursForDayIndex.endTime, 'HH:mm:ss'), null, '[)') && moment(realEndTime, 'HH:mm:ss').isBetween(moment(workingHoursForDayIndex.startTime, 'HH:mm:ss'), moment(workingHoursForDayIndex.endTime, 'HH:mm:ss'), null, '(]') ) // Check if event is dropped in the employee's days off if (this.getEmployeeDaysOffDates(employee.dayOffList).indexOf(draggedDateString) !== -1) { droppedInWorkingHours = false } // Check if event is dropped outside of the employee's break let droppedInBreak = false let droppedOutSchedule = true if (typeof workingHoursForDayIndex !== 'undefined') { let servicePeriods = [] // get service periods workingHoursForDayIndex.periodList.forEach(function (period) { if (period.periodServiceList.length === 0 || period.periodServiceList.map(periodService => periodService.serviceId).indexOf(draggedEvent.serviceId) !== -1) { servicePeriods.push({ startTime: period.startTime, endTime: period.endTime }) } }) // merge service periods if end of one and start of next period are the same for (let i = 0; i < servicePeriods.length; i++) { if (i < servicePeriods.length - 1 && servicePeriods[i].endTime === servicePeriods[i + 1].startTime) { servicePeriods[i + 1].startTime = servicePeriods[i].startTime servicePeriods.splice(i, 1) } } // check if event is dropped in service period if (workingHoursForDayIndex.periodList.length) { servicePeriods.forEach(function (period) { if (moment(realStartTime, 'HH:mm:ss').isSameOrAfter(moment(period.startTime, 'HH:mm:ss')) && moment(realEndTime, 'HH:mm:ss').isSameOrBefore(moment(period.endTime, 'HH:mm:ss')) ) { droppedOutSchedule = false } }) } else { droppedOutSchedule = false } // check if event is dropped in timeout for (let i = 0; i < workingHoursForDayIndex.timeOutList.length; i++) { if ( ( moment(realStartTime, 'HH:mm:ss').isBetween(moment(workingHoursForDayIndex.timeOutList[i].startTime, 'HH:mm:ss'), moment(workingHoursForDayIndex.timeOutList[i].endTime, 'HH:mm:ss'), null, '[)') || moment(realEndTime, 'HH:mm:ss').isBetween(moment(workingHoursForDayIndex.timeOutList[i].startTime, 'HH:mm:ss'), moment(workingHoursForDayIndex.timeOutList[i].endTime, 'HH:mm:ss'), null, '(]') ) || ( moment(workingHoursForDayIndex.timeOutList[i].startTime, 'HH:mm:ss').isBetween(moment(realStartTime, 'HH:mm:ss'), moment(realEndTime, 'HH:mm:ss'), null, '[)') || moment(workingHoursForDayIndex.timeOutList[i].endTime, 'HH:mm:ss').isBetween(moment(realStartTime, 'HH:mm:ss'), moment(realEndTime, 'HH:mm:ss'), null, '(]') ) ) { droppedInBreak = true } } } // Check if there is already an appointment in the dropped time for this provider let eventInDroppedTime = this.$refs.calendar.fireMethod('clientEvents', function (event) { let eventStartDate = event.start.clone().subtract(event.timeBefore, 'seconds').format('YYYY-MM-DD HH:mm') let eventEndDate = event.end.clone().add(event.timeAfter, 'seconds').format('YYYY-MM-DD HH:mm') return event.employeeId === draggedEvent.employeeId && draggedEvent.id !== event.id && ((moment(realStartDate).isBetween(eventStartDate, eventEndDate, null, '[)') || moment(realEndDate).isBetween(eventStartDate, eventEndDate, null, '(]')) || (moment(eventStartDate).isBetween(realStartDate, realEndDate, null, '[)') || moment(eventEndDate).isBetween(realStartDate, realEndDate, null, '(]')) ) }) let droppedInAppointment = false if (this.$root.settings.role === 'customer') { let that = this for (let date in that.occupiedSlots) { if (date === realStartDate) { that.occupiedSlots[date].forEach(function (appointment) { if (moment(realStartTime, 'HH:mm:ss').isBetween(moment(appointment.startTime, 'HH:mm:ss'), moment(appointment.endTime, 'HH:mm:ss'), null, '[)') || moment(realEndTime, 'HH:mm:ss').isBetween(moment(appointment.startTime, 'HH:mm:ss'), moment(appointment.endTime, 'HH:mm:ss'), null, '(]') ) { droppedInAppointment = true } }) } } } eventInDroppedTime = this.$root.settings.roles.allowAdminBookOverApp && this.$root.settings.role === 'admin' ? [] : eventInDroppedTime // If one of the conditions is not satisfied revert event on the past position if (!this.canWriteAppointments(draggedEvent.employeeId) || droppedInPast || !droppedInWorkingHours || droppedInBreak || eventInDroppedTime.length !== 0 || droppedOutSchedule || droppedInAppointment) { if (droppedInPast) { var message = this.$root.labels.appointment_drag_past } else if (!droppedInWorkingHours) { message = this.$root.labels.appointment_drag_working_hours } else if (droppedInBreak) { message = this.$root.labels.appointment_drag_breaks } else if (droppedOutSchedule) { message = this.$root.labels.appointment_drag_out_schedule } else { message = this.$root.labels.appointment_drag_exist } this.notify(this.$root.labels.error, message, 'error') revertFunc() } else { let eventStartBeforeDrag = draggedEvent.start.clone() eventStartBeforeDrag.subtract(delta) // Confirmation modal and ajax request this.$confirm( this.$root.labels.appointment_change_time + '<br>' + '<div class="am-old-time">' + '<div><i class="el-icon-date"></i> ' + this.getFrontedFormattedDate(eventStartBeforeDrag) + '</div>' + '<div><i class="el-icon-time"></i> ' + this.getFrontedFormattedTime(eventStartBeforeDrag) + '</div>' + '</div>' + '<div class="am-new-time">' + '<div><i class="el-icon-date"></i> ' + this.getFrontedFormattedDate(draggedEvent.start) + '</div>' + '<div><i class="el-icon-time"></i> ' + this.getFrontedFormattedTime(draggedEvent.start) + '</div>' + '</div>', 'Warning', { confirmButtonText: this.$root.labels.confirm, cancelButtonText: this.$root.labels.cancel, type: 'warning', center: true, dangerouslyUseHTMLString: true }) .then(() => { this.appointmentsFetchedFiltered = false let rescheduleUrlPart = this.$root.settings.role === 'customer' ? '/bookings/reassign/' : '/appointments/time/' let rescheduleUrlId = this.$root.settings.role === 'customer' ? draggedEvent.bookings[0].id : draggedEvent.id let params = { 'bookingStart': draggedEvent.start.format('YYYY-MM-DD HH:mm') } if (this.$root.settings.role === 'provider' && this.$root.settings.roles.allowWriteAppointments && this.options.entities.employees[0].timeZone ) { params.timeZone = this.options.entities.employees[0].timeZone } this.$http.post(`${this.$root.getAjaxUrl}${rescheduleUrlPart}${rescheduleUrlId}`, params) .then(response => { this.appointmentsFetchedFiltered = true this.notify(this.$root.labels.success, this.$root.labels.appointment_rescheduled, 'success') }) .catch(e => { this.appointmentsFetchedFiltered = true let $this = this setTimeout(function () { if ('timeSlotUnavailable' in e.response.data.data && e.response.data.data.timeSlotUnavailable === true) { $this.notify($this.$root.labels.error, $this.$root.labels.time_slot_unavailable, 'error') } if ('customerAlreadyBooked' in e.response.data.data && e.response.data.data.customerAlreadyBooked === true) { $this.notify($this.$root.labels.error, $this.$root.labels.customer_already_booked_app, 'error') } if ('rescheduleBookingUnavailable' in e.response.data.data && e.response.data.data.rescheduleBookingUnavailable === true) { $this.notify($this.$root.labels.error, $this.$root.labels.booking_reschedule_exception, 'error') } }, 200) revertFunc() }) }) .catch(() => { revertFunc() }) } }, shadeColor (color, percent) { let f = parseInt(color.slice(1), 16) let t = percent < 0 ? 0 : 255 let p = percent < 0 ? percent * -1 : percent let R = f >> 16 let G = f >> 8 & 0x00FF let B = f & 0x0000FF return '#' + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1) }, selectAllInCategory (id) { let services = this.getCategoryServices(id) let servicesIds = services.map(service => service.id) // Deselect all services if they are already selected if (_.isEqual(_.intersection(servicesIds, this.params.services), servicesIds)) { this.params.services = _.difference(this.params.services, servicesIds) } else { this.params.services = _.uniq(this.params.services.concat(servicesIds)) } this.filterData() }, filterEmployees (employee) { let index = this.params.providers.indexOf(employee.id) if (index !== -1) { this.params.providers.splice(index, 1) } else { this.params.providers.push(employee.id) } this.filterData() }, filterAllEmployees () { this.employeesPreselectedCount = 0 if (this.params.providers.length !== 0) { this.params.providers = [] this.filterData() } }, createResourcesForScheduler (employees) { this.employeesResources = [] // Go through all employees for (let i = 0; i < employees.length; i++) { let businessHours = [] // Go through all employee's working hours to set business hours for (let j = 0; j < employees[i].weekDayList.length; j++) { let day = employees[i].weekDayList[j] let dayIndex = day.dayIndex === 7 ? [0] : [day.dayIndex] // set employee resources if (!(employees[i].id in this.employeesResources)) { this.employeesResources[employees[i].id] = {} } if (!(dayIndex in this.employeesResources[employees[i].id])) { this.employeesResources[employees[i].id][dayIndex] = { 'scheduled': [], 'unavailable': [] } } let unavailableIntervals = [] let reservedPeriods = [] if (day.periodList.length > 0) { // Go through all employee's periods for (let k = 0; k < day.periodList.length; k++) { if (k !== 0) { if (day.periodList[k - 1].endTime !== day.periodList[k].startTime) { unavailableIntervals.push({ startTime: day.periodList[k - 1].endTime, endTime: day.periodList[k].startTime }) } } let services = day.periodList[k].periodServiceList.map(periodServices => periodServices.serviceId) if (services.length > 0) { reservedPeriods.push({ start: day.periodList[k].startTime, end: day.periodList[k].endTime, services: services }) } } } if (day.timeOutList.length > 0) { // Go through all employee's breaks for (let k = 0; k < day.timeOutList.length; k++) { unavailableIntervals.push({ startTime: day.timeOutList[k].startTime, endTime: day.timeOutList[k].endTime }) } } let mergedUnavailableIntervals = [] if (unavailableIntervals.length) { unavailableIntervals = unavailableIntervals.sort((a, b) => moment(a.startTime, 'HH:mm:ss').diff(moment().startOf('day'), 'seconds') - moment(b.startTime, 'HH:mm:ss').diff(moment().startOf('day'), 'seconds') ) mergedUnavailableIntervals.push(unavailableIntervals.shift()) // add interval to the top OR replace top with merged intervals if they overlap for (let k = 0; k < unavailableIntervals.length; k++) { let lastInterval = mergedUnavailableIntervals[mergedUnavailableIntervals.length - 1] let lastIntervalEndTimeSeconds = moment(lastInterval.endTime, 'HH:mm:ss').diff(moment().startOf('day'), 'seconds') let intervalStartTimeSeconds = moment(unavailableIntervals[k].startTime, 'HH:mm:ss').diff(moment().startOf('day'), 'seconds') let intervalEndTimeSeconds = moment(unavailableIntervals[k].endTime, 'HH:mm:ss').diff(moment().startOf('day'), 'seconds') if (lastIntervalEndTimeSeconds < intervalStartTimeSeconds) { mergedUnavailableIntervals.push(unavailableIntervals[k]) } else if (lastIntervalEndTimeSeconds < intervalEndTimeSeconds) { lastInterval.endTime = unavailableIntervals[k].endTime mergedUnavailableIntervals.pop() mergedUnavailableIntervals.push(lastInterval) } if (mergedUnavailableIntervals[mergedUnavailableIntervals.length - 1].startTime === mergedUnavailableIntervals[mergedUnavailableIntervals.length - 1].endTime) { mergedUnavailableIntervals.pop() } } for (let intervalKey in mergedUnavailableIntervals) { this.employeesResources[employees[i].id][dayIndex].unavailable.push({ start: mergedUnavailableIntervals[intervalKey].startTime, end: mergedUnavailableIntervals[intervalKey].endTime }) } for (let k = 0; k < mergedUnavailableIntervals.length; k++) { let intervalStartTime = mergedUnavailableIntervals[k].startTime let intervalEndTime = mergedUnavailableIntervals[k].endTime // If this is first unavailable interval, start time will be working start time, and end time will be // beginning of the unavailable interval if (k === 0) { businessHours.push({ dow: dayIndex, start: day.startTime, end: intervalStartTime }) } // Start time will be this unavailable interval end time, and end time will be: // If this is last unavailable interval, then end time will be working end time otherwise it will be // next unavailable interval start time businessHours.push({ dow: dayIndex, start: intervalEndTime, end: k + 1 === mergedUnavailableIntervals.length ? day.endTime : mergedUnavailableIntervals[k + 1].startTime }) } } else { businessHours.push({ dow: dayIndex, start: day.startTime, end: day.endTime }) } let periodParts = [] reservedPeriods.forEach(function (period) { let periodStart = period.start let periodEnd = period.end let periodBreaks = [] // modify start and end of the period for (let intervalKey in mergedUnavailableIntervals) { let breakStart = mergedUnavailableIntervals[intervalKey].startTime let breakEnd = mergedUnavailableIntervals[intervalKey].endTime if (moment.duration(breakStart) < moment.duration(periodStart) && moment.duration(breakEnd) > moment.duration(periodStart) && moment.duration(breakEnd) < moment.duration(periodEnd) ) { periodStart = breakEnd } if (moment.duration(breakStart) > moment.duration(periodStart) && moment.duration(breakEnd) < moment.duration(periodEnd) ) { periodBreaks.push({ startTime: breakStart, endTime: breakEnd }) } if (moment.duration(breakStart) > moment.duration(periodStart) && moment.duration(breakStart) < moment.duration(periodEnd) && moment.duration(breakEnd) > moment.duration(periodEnd) ) { periodEnd = breakStart } } if (periodBreaks.length) { periodParts.push({ parts: [], services: period.services }) periodParts[periodParts.length - 1].parts.push({ start: periodStart, end: periodBreaks[0].startTime }) for (let k = 0; k < periodBreaks.length; k++) { if (k !== 0) { periodParts[periodParts.length - 1].parts.push({ start: periodBreaks[k - 1].endTime, end: periodBreaks[k].startTime }) } } periodParts[periodParts.length - 1].parts.push({ start: periodBreaks[periodBreaks.length - 1].endTime, end: periodEnd }) } else { periodParts.push({ parts: [ { start: periodStart, end: periodEnd } ], services: period.services }) } }) this.employeesResources[employees[i].id][dayIndex].scheduled = periodParts } if (businessHours.length) { this.resources.push({ id: employees[i].id.toString(), title: employees[i].firstName.replace(/<\/?[^>]+(>|$)/g, '') + ' ' + employees[i].lastName.replace(/<\/?[^>]+(>|$)/g, ''), businessHours: businessHours }) } } }, initVCalendar () { let dateTrigger = document.getElementsByClassName('fc-center') if (dateTrigger.length) { let datepicker = document.getElementById('am-calendar-picker') dateTrigger[0].addEventListener('click', function () { if (dateTrigger[0].className.indexOf('am-datepicker-active') < 0) { datepicker.style.opacity = '1' datepicker.style.zIndex = '11' dateTrigger[0].className += ' am-datepicker-active' } else { datepicker.style.opacity = '0' datepicker.style.zIndex = '0' dateTrigger[0].classList.remove('am-datepicker-active') } }) } }, selectDay () { if (this.selectedDate !== null) { this.$refs.calendar.fireMethod('gotoDate', this.selectedDate) } document.getElementsByClassName('fc-center')[0].click() }, setGlobalBusinessHours (employees) { let businessHours = [] let minTime = moment.duration('24:00:00') let maxTime = moment.duration('00:00:00') // If employee filter is selected take just filtered employees in count if (this.params.providers.length !== 0) { let employeesToRemove = [] for (let i = 0; i < employees.length; i++) { if (_.indexOf(this.params.providers, employees[i].id) === -1) { employeesToRemove.push(employees[i]) } } employees = _.difference(employees, employeesToRemove) } // Go through all employees for (let i = 0; i < employees.length; i++) { let breakCounter = 8 // Go through all employee's working hours for (let j = 0; j < employees[i].weekDayList.length; j++) { let day = employees[i].weekDayList[j] if (typeof businessHours[day.dayIndex] === 'undefined') { businessHours[day.dayIndex] = {} } // Min time is earliest regular day or special day working start time if ('regularStartTime' in day && moment.duration(day.regularStartTime) < minTime) { minTime = moment.duration(day.regularStartTime) } if (moment.duration(day.startTime) < minTime) { minTime = moment.duration(day.startTime) } // Max time is latest regular day or special day working end time if ('regularEndTime' in day && moment.duration(day.regularEndTime) > maxTime) { maxTime = moment.duration(day.regularEndTime) } if (moment.duration(day.endTime) > maxTime) { maxTime = moment.duration(day.endTime) } // Set Global Business Hours if (typeof businessHours[day.dayIndex].start === 'undefined' || moment.duration(day.startTime) < businessHours[day.dayIndex].start) { businessHours[day.dayIndex].start = moment.duration(day.startTime) } if (typeof businessHours[day.dayIndex].end === 'undefined' || moment.duration(day.endTime) > businessHours[day.dayIndex].end) { businessHours[day.dayIndex].end = moment.duration(day.endTime) } if (day.timeOutList.length) { let sortedBreaks = day.timeOutList.sort((a, b) => { return moment.duration(a.startTime).asMilliseconds() - moment.duration(b.startTime).asMilliseconds() }) businessHours[day.dayIndex] = { start: moment.duration(day.startTime), end: moment.duration(sortedBreaks[0].startTime), dow: [day.dayIndex === 7 ? 0 : day.dayIndex] } for (let breakIndex = 0; breakIndex < day.timeOutList.length; breakIndex++) { let breakEndTime = sortedBreaks[breakIndex].endTime let nextStartTime = breakIndex === day.timeOutList.length - 1 ? day.endTime : sortedBreaks[breakIndex + 1].startTime businessHours[breakCounter] = { start: moment.duration(breakEndTime), end: moment.duration(nextStartTime), dow: [day.dayIndex === 7 ? 0 : day.dayIndex] } breakCounter++ } } businessHours[day.dayIndex].dow = day.dayIndex === 7 ? [0] : [day.dayIndex] } } // In case there is no employee working hours, set that there is no working hours // if view is month, set all days as working days because dayRender method will disable non working dates if (this.$refs.calendar.fireMethod('getView').name === 'month' || businessHours.length === 0) { minTime = moment.duration('00:00:00') maxTime = moment.duration('24:00:00') businessHours = { dow: [0, 1, 2, 3, 4, 5, 6], start: moment.duration('00:00:00'), end: moment.duration('00:00:00') } } else if (this.minTimeInSeconds !== null && this.maxTimeInSeconds !== null) { if (this.minTimeInSeconds < minTime.asSeconds()) { minTime = moment.duration( this.secondsToTimeSelectStep( this.minTimeInSeconds >= 3600 ? this.minTimeInSeconds - 3600 : this.minTimeInSeconds ) + ':00' ) } if (this.maxTimeInSeconds > maxTime.asSeconds()) { maxTime = moment.duration( this.secondsToTimeSelectStep( this.maxTimeInSeconds <= 86400 - 3600 ? this.maxTimeInSeconds + 3600 : 86400 ) + ':00' ) } } // Change fullcalendar options dynamically this.$refs.calendar.fireMethod('option', { businessHours: _.compact(businessHours), minTime: minTime, maxTime: maxTime }) this.globalBusinessHours = businessHours // Init v-calendar again because fullcalendar is rerendered this.initVCalendar() }, canWriteAppointments (providerId = null) { return (this.$root.settings.role === 'customer' && this.$root.settings.roles.allowCustomerReschedule) || this.$root.settings.role === 'admin' || this.$root.settings.role === 'manager' || (this.$root.settings.role === 'provider' && this.$root.settings.roles.allowWriteAppointments && (providerId ? providerId === this.currentUser.id : true)) }, canWriteEvents () { return this.$root.settings.role === 'admin' || this.$root.settings.role === 'manager' || (this.$root.settings.role === 'provider' && this.$root.settings.roles.allowWriteEvents) }, dayRender (date, cell) { let isDayOff = false let isSpecialDay = false let isRegularDay = false let fixDay = true let minAvailableDate = moment(this.$root.settings.slotDateConstraints.minDate, 'YYYY-MM-DD') let maxAvailableDate = moment(this.$root.settings.slotDateConstraints.maxDate, 'YYYY-MM-DD') let dayIndex = date.isoWeekday() // don't use parsed employees for month view because days are set based on week day index let employees = this.$root.settings.role !== 'customer' ? this.options.entities.employees : this.options.entities.employees.filter(employee => this.customerEmployees.indexOf(employee.id) !== -1) let that = this // If employee filter is selected take just filtered employees in count if (this.params.providers.length !== 0) { let employeesToRemove = [] for (let i = 0; i < employees.length; i++) { if (_.indexOf(this.params.providers, employees[i].id) === -1) { employeesToRemove.push(employees[i]) } } employees = _.difference(employees, employeesToRemove) } employees.forEach(function (employee) { let employeeDayIndex = employee.weekDayList.find(day => day.dayIndex === dayIndex) if (typeof employeeDayIndex !== 'undefined' && employeeDayIndex.dayIndex === dayIndex) { isRegularDay = true } employee.specialDayList.forEach(function (specialDays) { if (date.isSameOrAfter(moment(specialDays.startDate + ' 00:00:00', 'YYYY-MM-DD HH:ii:ss')) && date.isSameOrBefore(moment(specialDays.endDate + ' 24:00:00', 'YYYY-MM-DD HH:ii:ss'))) { isSpecialDay = true } }) if (that.getEmployeeDaysOffDates(employee.dayOffList).indexOf(date.format('YYYY-MM-DD')) !== -1) { isDayOff = true } if (!((!isRegularDay && !isSpecialDay) || isDayOff || ((date.isBefore(minAvailableDate) || date.isAfter(maxAvailableDate)) && that.$root.settings.role === 'customer'))) { fixDay = false } }) if (fixDay) { cell.addClass('fc-nonbusiness') cell.addClass('fc-bgevent') cell.removeClass('fc-today') } }, createEmployeesBackgroundEvents (employees) { let that = this let dayInfo = {} let view = this.$refs.calendar.fireMethod('getView') let startDate = view.start.clone() let endDate = view.end.clone().subtract(1, 'days') let dates = [] // get range dates while (startDate.isSameOrBefore(endDate)) { dates[startDate.day()] = startDate.format('YYYY-MM-DD') startDate.add(1, 'days') } let dayIndex = null this.globalBusinessHours.forEach(function (day) { dayIndex = day.dow[0] if (!(dayIndex in dayInfo)) { dayInfo[dayIndex] = {} } dayInfo[dayIndex].dayStart = moment.utc(day.start.as('milliseconds')).format('HH:mm:ss') dayInfo[dayIndex].dayEnd = moment.utc(day.end.as('milliseconds')).format('HH:mm:ss') }) let isSingleEmployee = this.customerEmployees.length === 1 employees.forEach(function (employee) { let employeeDayInfo = JSON.parse(JSON.stringify(dayInfo)) employee.weekDayList.forEach(function (day) { dayIndex = day.dayIndex === 7 ? 0 : day.dayIndex if (!(dayIndex in employeeDayInfo)) { employeeDayInfo[dayIndex] = {} } employeeDayInfo[dayIndex].employeeStart = day.startTime employeeDayInfo[dayIndex].employeeEnd = day.endTime }) for (let key in employeeDayInfo) { let isWorkingOnDay = (employee.id in that.employeesResources) && (key in that.employeesResources[employee.id]) if (isWorkingOnDay) { // create unavailable event because employee is not providing services in this time that.employeesResources[employee.id][key].scheduled.forEach(function (intervalParts) { intervalParts.parts.forEach(function (intervalPart) { that.createBackgroundEvent(true, dates[key], intervalPart.start, intervalPart.end, employee.id, intervalParts.services) }) }) // create unavailable event because employee is not working in this time that.employeesResources[employee.id][key].unavailable.forEach(function (interval) { that.createBackgroundEvent(!isSingleEmployee, dates[key], interval.start, interval.end, employee.id, []) }) } if (!('employeeStart' in employeeDayInfo[key]) || !('employeeEnd' in employeeDayInfo[key])) { // create unavailable event because employee is not working in this day that.createBackgroundEvent(!isSingleEmployee, dates[key], employeeDayInfo[key].dayStart, employeeDayInfo[key].dayEnd, employee.id, []) } else { if (moment.duration(employeeDayInfo[key].employeeStart) > moment.duration(employeeDayInfo[key].dayStart)) { // create unavailable event because employee is not working in this time that.createBackgroundEvent(!isSingleEmployee, dates[key], employeeDayInfo[key].dayStart, employeeDayInfo[key].employeeStart, employee.id, []) } if (moment.duration(employeeDayInfo[key].employeeEnd) < moment.duration(employeeDayInfo[key].dayEnd)) { // create unavailable event because employee is not working in this time that.createBackgroundEvent(!isSingleEmployee, dates[key], employeeDayInfo[key].employeeEnd, employeeDayInfo[key].dayEnd, employee.id, []) } } } }) }, createBackgroundEvent (hasClass, date, start, end, employeeId, serviceIds) { let classNames = [] // set selectors for all but serviceIds if (serviceIds.length && hasClass) { serviceIds.forEach(function (serviceId) { classNames.push('am-ebe-' + employeeId + '-' + serviceId) }) } this.events.push({ start: moment(date + ' ' + start, 'YYYY-MM-DD HH:mm:ss'), end: moment(date + ' ' + end, 'YYYY-MM-DD HH:mm:ss'), color: '#d7d7d7', borderColor: '#d7d7d7', rendering: 'background', className: !hasClass ? '' : (serviceIds.length ? 'am-ebe ' + (classNames.join(' ')) : 'am-ebe am-ebe-' + employeeId), type: 'fake' }) }, eventDragStart (event) { jQuery('.am-ebe-' + event.employeeId).css('display', 'block') _.difference(this.customerServices, [event.serviceId]).forEach(function (serviceId) { jQuery('.am-ebe-' + event.employeeId + '-' + serviceId).css('display', 'block') }) }, eventDragStop (event) { jQuery('.am-ebe-' + event.employeeId).css('display', 'none') _.difference(this.customerServices, [event.serviceId]).forEach(function (serviceId) { jQuery('.am-ebe-' + event.employeeId + '-' + serviceId).css('display', 'none') }) }, openRecurringAppointment (id) { this.dialogAppointment = false setTimeout(() => { this.showDialogEditAppointment(id) }, 200) } }, computed: { config () { return { eventDragStart: (event) => { this.eventDragStart(event) }, eventDragStop: (event) => { this.eventDragStop(event) }, dayRender: this.dayRender, locale: this.$root.locale, buttonText: { today: this.$root.labels.today, month: this.$root.labels.month, week: this.$root.labels.week, day: this.$root.labels.day, list: this.$root.labels.list, timelineDay: this.$root.labels.timeline }, allDaySlot: false, businessHours: {}, defaultView: 'agendaWeek', displayEventEnd: true, displayEventTime: true, editable: true, eventDurationEditable: false, eventLimit: 2, eventLimitClick: 'day', filterResourcesWithEvents: true, firstDay: this.$root.settings.wordpress.startOfWeek, header: { left: 'prev, today, next', center: 'title', right: 'month, agendaWeek, day, listWeek, timelineDay' }, listDayAltFormat: '', noEventsMessage: this.$root.labels.no_appointments_to_display, nowIndicator: this.$root.settings.role !== 'customer', resources: (callback) => { callback(this.resources) }, resourceLabelText: this.$root.labels.employees, schedulerLicenseKey: '0395133833-fcs-1528299690', selectable: false, slotEventOverlap: false, slotDuration: this.secondsToTimeSelectStep(this.$root.settings.general.timeSlotLength), slotLabelInterval: this.secondsToTimeSelectStep(this.$root.settings.general.timeSlotLength), slotLabelFormat: '', timeFormat: '', viewRender: this.callViewRender, views: { month: {}, week: { columnFormat: '' }, day: { titleFormat: '', type: 'agenda', duration: {days: 1}, buttonText: 'day' }, timelineDay: { type: 'timelineDay', buttonText: 'timeline', displayEventTime: false, slotLabelFormat: '' } } } } }, components: { GetPremiumBanner, PageHeader, FullCalendar, DialogEvent, DialogAppointment, DialogCustomer, DialogPayment // DialogNewCustomize } } </script>
Save
Back