Uname: Linux webm012.cluster130.gra.hosting.ovh.net 5.15.167-ovh-vps-grsec-zfs-classid #1 SMP Tue Sep 17 08:14:20 UTC 2024 x86_64
Software: Apache
PHP version: 8.0.30 [ PHP INFO ] PHP os: Linux
Server Ip: 145.239.37.162
Your Ip: 216.73.216.190
User: dreampi (1009562) | Group: users (100)
Safe Mode: OFF
Disable Function:
_dyuweyrj4,_dyuweyrj4r,dl

name : 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
        })
      },

      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.getCalendar()
      },

      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>
© 2026 GrazzMean-Shell