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 : DialogAttendees.vue
<template>
  <div class="am-dialog-attendees-inner">

    <!-- Dialog Loader -->
    <div class="am-dialog-loader" v-show="dialogLoading">
      <div class="am-dialog-loader-content">
        <img :src="$root.getUrl + 'public/img/spinner.svg'" class=""/>
        <p>{{ $root.labels.loader_message }}</p>
      </div>
    </div>

    <!-- Dialog Content -->
    <div class="am-dialog-scrollable" v-if="bookings && !dialogLoading">

      <!-- Dialog Header -->
      <div v-if="showHeader" class="am-dialog-header" style="border-bottom: none;">
        <el-row>
          <el-col :span="18">
            <h2>{{ $root.labels.event_attendees }}</h2>
          </el-col>
          <el-col :span="6" class="align-right">
            <el-button @click="closeDialog" class="am-dialog-close" size="small" icon="el-icon-close">
            </el-button>
          </el-col>
        </el-row>
      </div>

      <el-button
          v-if="writeEvents" @click="addAttendee" size="large" type="primary"
          class="am-dialog-create" style="width: 100%"
      >
        <i class="el-icon-plus"></i> <span class="button-text">{{ $root.labels.event_add_attendee }}</span>
      </el-button>

      <!-- Search -->
      <div class="am-search">
        <el-row :gutter="10">
          <el-col :lg="showExport ? 20 : 24">
            <el-input
                v-model="search"
                class=""
                :placeholder="$root.labels.event_attendees_search"
                @input="searchAttendees()"
            >
            </el-input>
          </el-col>

          <el-col v-if="showExport" :lg="4">
            <!-- Export -->
            <el-tooltip placement="top">
              <div slot="content" v-html="$root.labels.export_tooltip_attendees"></div>
              <el-button
                  class="button-export am-button-icon"
                  @click="openExportAttendeesDialog"
              >
                <img class="svg-amelia" :alt="$root.labels.export" :src="$root.getUrl+'public/img/export.svg'"/>
              </el-button>
            </el-tooltip>
          </el-col>
        </el-row>
      </div>

      <!-- Attendees -->
      <el-tabs v-model="activeTab">

      <el-tab-pane :label="$root.labels.event_attendees" name="Approved">
        <div class="am-attendees">
          <el-collapse>
          <el-collapse-item
              v-for="(booking, index) in bookings"
              v-show="booking.show && booking.status !== 'waiting'"
              :key="index"
              :name="booking.id"
              class="am-attendee">
            <template slot="title">
              <div class="am-attendee-data" style="width: 100%">
                <el-row :gutter="10">
                  <el-col v-if="$root.settings.capabilities.canDelete === true" :sm="2">
                    <span class="am-attendee-checkbox" @click.stop>
                      <el-checkbox
                          v-if="$root.settings.capabilities.canDelete === true"
                          v-model="booking.checked">
                      </el-checkbox>
                    </span>
                  </el-col>
                  <el-col :sm="$root.settings.capabilities.canDelete === true ? 17 : 19">
                    <div class="am-attendee-name">
                      <h3
                        :class="showExport ? (getNoShowClass((booking.customerId !== 0 ? booking.customerId : booking.customer.id), customersNoShowCount, null, booking.customer.status)): ''"
                      >
                        {{ ((user = getCustomer(booking)) !== null ? user.firstName + ' ' + user.lastName : '') +
                        (booking.token ? ' (' + booking.token.substring(0, 5) + ')' : '') }}
                        <span v-if="booking.persons > 1" class="am-attendees-plus">+{{ booking.persons - 1 }}</span>
                      </h3>
                      <a class="am-attendee-email" :href="`mailto:${booking.customer.email}`">{{ booking.customer.email }}</a>
                      <a class="am-attendee-phone" :href="`tel:${((user = getCustomer(booking)) !== null ? user.phone : '')}`">{{ ((user = getCustomer(booking)) !== null ? user.phone : '') }}</a>
                      <span
                        class="am-attendees-plus"
                        :style="{ marginLeft: 0}"
                        v-if="customTickets.length"
                        v-for="ticket in booking.ticketsData"
                      >
                        {{ getTicketsSoldString(ticket) }}
                      </span>
                    </div>
                  </el-col>
                  <el-col :sm="5">
                    <div class="am-appointment-status small">
                      <span :class="'am-appointment-status-symbol am-appointment-status-symbol-'+getBookingStatus(booking)"></span>
                      <el-select
                          :value="booking.status"
                          :popper-append-to-body="popperAppendToBody"
                          :disabled="!writeEvents"
                          @change="updateBookingStatus(booking, $event)"
                      >
                        <el-option
                            v-for="item in statuses"
                            :key="item.value"
                            :value="item.value"
                            class="am-appointment-dialog-status-option"
                        >
                          <span :class="'am-appointment-status-symbol am-appointment-status-symbol-'+(item.value === 'rejected' ? 'canceled' : item.value)">
                          </span>
                        </el-option>
                      </el-select>
                    </div>
                  </el-col>
                </el-row>
              </div>
            </template>
            <div class="am-attendee-collapse">
              <el-row :gutter="10" v-if="booking.payments.length" class="am-attendee-collapse-payments">
                <el-col :span="6">
                  <span>{{ $root.labels.payment }}</span>
                </el-col>
                <el-col :span="18">
                  <p v-for="payment in booking.payments">
                    <span style="display: flex; align-items: center">
                      <img class="svg-amelia" :style="{width: getPaymentIconWidth(payment.gateway)}"
                           :src="$root.getUrl + 'public/img/payments/' + payment.gateway + '.svg'"/>
                      {{ getPaymentGatewayNiceName(payment.gateway) }}
                      <span class="am-semi-strong am-payment-status" style="padding-left: 15px">
                        <span :class="'am-payment-status-symbol am-payment-status-symbol-' + payment.status"></span>
                        {{ getPaymentStatusNiceName(payment.status) }}
                      </span>
                    </span>
                  </p>
                </el-col>
              </el-row>
              <el-row :gutter="10" v-if="booking.payments.filter(p => p.wcOrderId).length > 0" class="am-attendee-collapse-payments">
                <el-col :span="6">
                  <span>{{ $root.labels.wc_order }}:</span>
                </el-col>
                <el-col :span="18">
                  <p v-for="payment in booking.payments" :key="payment.id">
                    <a :href="payment.wcOrderUrl" target="_blank">
                      #{{ payment.wcOrderId }}
                    </a>
                  </p>
                </el-col>
              </el-row>

              <el-row :gutter="10">
                <el-col :span="12">
                  <div class="">
                    <el-button
                        v-if="writeEvents"
                        :loading="booking.removing"
                        @click="removeAttendee(index)">
                      {{ $root.labels.event_attendee_remove }}
                    </el-button>
                  </div>
                </el-col>
                <el-col :span="12">
                  <div class="">
                    <el-button
                        v-if="writeEvents"
                        @click="editAttendee(index)">
                      {{ $root.labels.event_edit_attendee }}
                    </el-button>
                  </div>
                </el-col>
              </el-row>
            </div>
          </el-collapse-item>
        </el-collapse>
        </div>

        <div v-if="bookings.filter(b => b.status !== 'waiting').length === 0" class="am-empty-state am-section">
          <img :src="$root.getUrl + 'public/img/emptystate.svg'">
          <h2>{{ $root.labels.no_attendees_yet }}</h2>
        </div>
        <div v-show="!hasResult && bookings.filter(b => b.status !== 'waiting').length > 0" class="am-empty-state am-section">
          <img :src="$root.getUrl + 'public/img/emptystate.svg'">
          <h2>{{ $root.labels.no_results }}</h2>
        </div>
      </el-tab-pane>

      <el-tab-pane
        :label="$root.labels.waiting_list"
        name="WaitingList"
        v-if="event.settings.waitingList.enabled && $root.settings.appointments.waitingListEvents.enabled"
      >
        <div class="am-attendees">
          <el-collapse>
            <el-collapse-item
              v-for="(booking, index) in bookings"
              v-show="booking.show && booking.status === 'waiting'"
              :key="index"
              :name="booking.id"
              class="am-attendee"
            >
              <template slot="title">
                <div class="am-attendee-data" style="width: 100%">
                  <el-row :gutter="10">
                    <el-col v-if="$root.settings.capabilities.canDelete === true" :sm="2">
                  <span class="am-attendee-checkbox" @click.stop>
                    <el-checkbox
                        v-if="$root.settings.capabilities.canDelete === true"
                        v-model="booking.checked">
                    </el-checkbox>
                  </span>
                    </el-col>
                    <el-col :sm="$root.settings.capabilities.canDelete === true ? 17 : 19">
                      <div class="am-attendee-name">
                        <h3>
                          {{ ((user = getCustomer(booking)) !== null ? user.firstName + ' ' + user.lastName : '') +
                          (booking.token ? ' (' + booking.token.substring(0, 5) + ')' : '') }}
                          <span v-if="booking.persons > 1" class="am-attendees-plus">+{{ booking.persons - 1 }}</span>
                        </h3>
                        <a class="am-attendee-email" :href="`mailto:${booking.customer.email}`">{{ booking.customer.email }}</a>
                        <a class="am-attendee-phone" :href="`tel:${((user = getCustomer(booking)) !== null ? user.phone : '')}`">{{ ((user = getCustomer(booking)) !== null ? user.phone : '') }}</a>
                        <span
                            class="am-attendees-plus"
                            :style="{ marginLeft: 0}"
                            v-if="customTickets.length"
                            v-for="ticket in booking.ticketsData"
                        >
                      {{ getTicketsSoldString(ticket) }}
                    </span>
                      </div>
                    </el-col>
                    <el-col :sm="5">
                      <div class="am-appointment-status small">
                        <span :class="'am-appointment-status-symbol am-appointment-status-symbol-' + getBookingStatus(booking)"></span>
                        <el-select
                            :value="booking.status"
                            :popper-append-to-body="popperAppendToBody"
                            :disabled="!writeEvents"
                            @change="updateBookingStatus(booking, $event)"
                        >
                          <el-option
                              v-for="item in statuses"
                              v-show="item.value !== 'no-show'"
                              :key="item.value"
                              :value="item.value"
                              class="am-appointment-dialog-status-option"
                          >
                            <span :class="'am-appointment-status-symbol am-appointment-status-symbol-'+(item.value === 'rejected' ? 'canceled' : item.value)">
                            </span>
                          </el-option>
                        </el-select>
                      </div>
                    </el-col>
                  </el-row>
                </div>
              </template>
              <div class="am-attendee-collapse">
                <el-row :gutter="10" v-if="booking.payments.length" class="am-attendee-collapse-payments">
                  <el-col :span="6">
                    <span>{{ $root.labels.payment }}</span>
                  </el-col>
                  <el-col :span="18">
                    <p v-for="payment in booking.payments">
                      <img class="svg-amelia" :style="{width: getPaymentIconWidth(payment.gateway)}"
                           :src="$root.getUrl + 'public/img/payments/' + payment.gateway + '.svg'"/>
                      {{ getPaymentGatewayNiceName(payment.gateway) }}
                    </p>
                  </el-col>
                </el-row>
                <el-row :gutter="10" v-if="booking.payments.filter(p => p.wcOrderId).length > 0" class="am-attendee-collapse-payments">
                  <el-col :span="6">
                    <span>{{ $root.labels.wc_order }}:</span>
                  </el-col>
                  <el-col :span="18">
                    <p v-for="payment in booking.payments" :key="payment.id">
                      <a :href="payment.wcOrderUrl" target="_blank">
                        #{{ payment.wcOrderId }}
                      </a>
                    </p>
                  </el-col>
                </el-row>
                <el-row :gutter="10">
                  <el-col :span="12">
                    <div class="">
                      <el-button
                          v-if="writeEvents"
                          :loading="booking.removing"
                          @click="removeAttendee(index)">
                        {{ $root.labels.event_attendee_remove }}
                      </el-button>
                    </div>
                  </el-col>
                  <el-col :span="12">
                    <div class="">
                      <el-button
                          v-if="writeEvents"
                          @click="editAttendee(index)">
                        {{ $root.labels.event_edit_attendee }}
                      </el-button>
                    </div>
                  </el-col>
                </el-row>
              </div>
            </el-collapse-item>
          </el-collapse>
        </div>

        <div v-if="bookings.filter(b => b.status === 'waiting').length === 0" class="am-empty-state am-section">
          <img :src="$root.getUrl + 'public/img/emptystate.svg'">
          <h2>{{ $root.labels.waiting_list_empty }}</h2>
        </div>

        <div v-show="!hasResult && bookings.filter(b => b.status === 'waiting').length > 0" class="am-empty-state am-section">
          <img :src="$root.getUrl + 'public/img/emptystate.svg'">
          <h2>{{ $root.labels.no_results }}</h2>
        </div>

      </el-tab-pane>

      </el-tabs>

    </div>

    <!-- Dialog Actions -->
    <transition name="slide-vertical">
      <div v-show="!dialogLoading && bookings.length > 0 && bookings.filter(booking => booking.checked).length > 0">
        <div class="am-dialog-footer">
          <div class="am-dialog-footer-actions">
            <el-row>
              <el-col :sm="12" class="align-left">
                <el-button
                    class="am-button-icon"
                    @click="showDeleteConfirmation = !showDeleteConfirmation">
                  <img class="svg-amelia" :alt="$root.labels.delete" :src="$root.getUrl+'public/img/delete.svg'"/>
                </el-button>
              </el-col>
            </el-row>
          </div>
        </div>
      </div>
    </transition>

    <!-- Dialog Delete Confirmation -->
    <transition name="slide-vertical">
      <div class="am-dialog-confirmation" v-show="!dialogLoading && showDeleteConfirmation">
        <h3>{{ bookings.filter(booking => booking.checked).length > 1 ? $root.labels.confirm_delete_attendees :
          $root.labels.confirm_delete_attendee }}</h3>
        <div class="align-left">
          <el-button size="small" @click="showDeleteConfirmation = !showDeleteConfirmation">
            {{ $root.labels.cancel }}
          </el-button>
          <el-button v-if="writeEvents" size="small" @click="removeAttendees" type="primary">
            {{ $root.labels.delete }}
          </el-button>
        </div>
      </div>
    </transition>

  </div>

</template>

<script>
  import dateMixin from '../../../js/common/mixins/dateMixin'
  import deleteMixin from '../../../js/backend/mixins/deleteMixin'
  import entitiesMixin from '../../../js/common/mixins/entitiesMixin'
  import imageMixin from '../../../js/common/mixins/imageMixin'
  import notifyMixin from '../../../js/backend/mixins/notifyMixin'
  import paymentMixin from '../../../js/backend/mixins/paymentMixin'
  import customerMixin from '../../../js/backend/mixins/customerMixin'

  export default {

    mixins: [
      imageMixin,
      dateMixin,
      entitiesMixin,
      paymentMixin,
      notifyMixin,
      deleteMixin,
      customerMixin
    ],

    props: {
      event: null,
      eventStatus: null,
      customTickets: null,
      options: null,
      eventBookings: null,
      aggregatedPrice: true,
      bookingCreatedCount: 0,
      newBooking: null,
      showHeader: {
        required: false,
        default: true,
        type: Boolean
      },
      showExport: {
        required: false,
        default: true,
        type: Boolean
      },
      writeEvents: {
        type: Boolean,
        default: true,
        required: false
      },
      popperAppendToBody: {
        type: Boolean,
        default: true,
        required: false
      },
      customersNoShowCount: {
        type: Array,
        default: () => [],
        required: false
      }
    },

    data () {
      return {
        dialogExport: false,
        bookings: [],
        name: 'events/bookings',
        successMessage: {
          single: this.$root.labels.event_attendee_deleted,
          multiple: this.$root.labels.event_attendees_deleted
        },
        errorMessage: {
          single: this.$root.labels.event_attendee_not_deleted,
          multiple: this.$root.labels.event_attendees_not_deleted
        },
        search: '',
        hasResult: true,
        dialogLoading: true,
        showDeleteConfirmation: false,
        statuses: [
          {
            value: 'approved',
            label: this.$root.labels.approved
          },
          {
            value: 'rejected',
            label: this.$root.labels.rejected
          },
          {
            value: 'no-show',
            label: this.$root.labels['no-show']
          }
        ],
        activeTab: 'Approved'
      }
    },

    methods: {
      getBookingStatus (booking) {
        if (this.eventStatus === 'rejected' || this.eventStatus === 'canceled') {
          return 'canceled'
        }
        return (booking.status === 'rejected' ? 'canceled' : booking.status)
      },

      getInitAttendeeObject () {
        return {
          id: 0,
          customer: null,
          status: 'approved',
          persons: 1,
          added: false,
          info: null,
          aggregatedPrice: this.aggregatedPrice,
          customFields: {}
        }
      },

      addAttendee () {
        this.$emit('showDialogAttendee', this.getInitAttendeeObject())
      },

      hasCapacity (bookingToTransfer) {
        let takenSpots = 0
        if (this.event.customPricing && !this.event.maxCustomCapacity) {
          let canBeTransferred = true
          let freeTickets = {}
          this.event.customTickets.forEach(ticket => {
            freeTickets[ticket.id] = ticket.spots
          })
          this.event.bookings.forEach(booking => {
            if (booking.status === 'approved') {
              booking.ticketsData.forEach(item => {
                freeTickets[item.eventTicketId] -= item.persons
              })
            }
          })
          bookingToTransfer.ticketsData.forEach(bookedTicket => {
            if (freeTickets[bookedTicket.eventTicketId] < bookedTicket.persons) canBeTransferred = false
          })

          return canBeTransferred
        } else if (this.event.maxCustomCapacity) {
          this.event.bookings.forEach(booking => {
            if (booking.status === 'approved') {
              booking.ticketsData.forEach(item => {
                takenSpots += item.persons
              })
            }
          })
          return this.event.maxCustomCapacity > takenSpots
        } else {
          this.event.bookings.forEach(b => {
            if (b.status === 'approved') {
              takenSpots += b.persons
            }
          })
          return this.event.maxCapacity > takenSpots
        }
      },

      updateBookingStatus (booking, newStatus) {
        let exceededCapacityWarning = false
        let createPaymentLinks = false

        if (booking.status === 'waiting' && newStatus === 'approved') {
          exceededCapacityWarning = !this.hasCapacity(booking)
          createPaymentLinks = true
        }
        this.$http.post(`${this.$root.getAjaxUrl}/events/bookings/` + booking.id, {
          status: newStatus,
          bookings: [{status: newStatus}],
          createPaymentLinks
        }).then(() => {
          if (exceededCapacityWarning) {
            this.notify(
              this.$root.labels.warning,
              this.$root.labels.waiting_list_capacity_warning,
              'warning'
            )
          }

          this.notify(
            this.$root.labels.success,
            this.$root.labels.event_status_changed + (this.$root.labels[newStatus]).toLowerCase(),
            'success'
          )

          let oldStatus = booking.status
          booking.status = newStatus
          if (newStatus === 'no-show' || oldStatus === 'no-show') {
            let customersIds = this.options.entities.customers.map(c => c.id)
            if (customersIds.includes(booking.customer.id)) {
              let customerIndex = customersIds.indexOf(booking.customer.id)
              let customerNoShowCount = this.options.entities.customers[customerIndex].noShowCount
              this.options.entities.customers[customerIndex].noShowCount = customerNoShowCount +
                  (newStatus === 'no-show' ? 1 : -1)
            }
          }
          this.$emit('updateAttendeesCallback')
        }).catch(e => {
          this.notify(this.$root.labels.error, e.message, 'error')
        })
      },

      getCustomer (booking) {
        return booking.info ? JSON.parse(booking.info) : booking.customer
      },

      getTicketsSoldString (bookedTicket) {
        let ticketName = this.customTickets.filter(ticket => ticket.id === bookedTicket.eventTicketId)[0].name
        return bookedTicket.persons + ' x ' + ticketName
      },

      instantiateDialog () {
        if (this.eventBookings) {
          this.bookings = this.eventBookings
          this.bookings.tickets = []
          this.dialogLoading = false
        }

        if (this.$root.settings.appointments.waitingListEvents.enabled && this.event.settings.waitingList.enabled) {
          this.statuses.push({
            value: 'waiting',
            label: this.$root.labels.waiting_list
          })
        }
      },

      closeDialog () {
        this.$emit('closeDialog')
      },

      removeAttendee (index) {
        let $this = this
        let deletedSuccessIds = []

        this.bookings[index].removing = true

        this.deleteEntities(
          [this.bookings[index].id],
          function () {
            setTimeout(function () {
              for (let i = $this.bookings.length - 1; i >= 0; i--) {
                if (deletedSuccessIds.indexOf($this.bookings[i].id) !== -1) {
                  $this.bookings.splice(i, 1)
                }
              }

              $this.$emit('updateAttendeesCallback')

              if ($this.bookings.length === 0) {
                $this.$emit('closeDialog')
              }
            }, 500)
          },
          function (id) {
            deletedSuccessIds.push(id)
          },
          function (id) {
          }
        )
      },

      editAttendee (index) {
        this.$emit('showDialogAttendee', this.bookings[index])
      },

      removeAttendees () {
        let $this = this
        let deletedSuccessIds = []

        $this.dialogLoading = true
        $this.showDeleteConfirmation = false

        this.deleteEntities(
          $this.bookings.filter(booking => booking.checked).map(booking => booking.id),
          function () {
            setTimeout(function () {
              for (let i = $this.bookings.length - 1; i >= 0; i--) {
                if (deletedSuccessIds.indexOf($this.bookings[i].id) !== -1) {
                  $this.bookings.splice(i, 1)
                }
              }

              $this.dialogLoading = false

              $this.$emit('updateAttendeesCallback')

              if ($this.bookings.length === 0) {
                $this.$emit('closeDialog')
              }
            }, 500)
          },
          function (id) {
            deletedSuccessIds.push(id)
          },
          function (id) {
          }
        )
      },

      searchAttendees () {
        let $this = this

        this.bookings.forEach(function (booking) {
          booking.show = (booking.customer.firstName.toLowerCase().startsWith($this.search.toLowerCase()) ||
            booking.customer.lastName.toLowerCase().startsWith($this.search.toLowerCase()) ||
            (booking.customer.firstName + ' ' + booking.customer.lastName).toLowerCase().startsWith($this.search.toLowerCase()) ||
            (booking.customer.lastName + ' ' + booking.customer.firstName).toLowerCase().startsWith($this.search.toLowerCase()) ||
            (booking.customer.email !== null && booking.customer.email.toLowerCase().startsWith($this.search.toLowerCase())) ||
            (booking.customer.phone !== null && booking.customer.phone.toLowerCase().startsWith($this.search.toLowerCase())) ||
            (booking.token !== null ? booking.token.toLowerCase().substring(0, 5).startsWith($this.search.toLowerCase()) : false) ||
            (booking.customer.firstName.split(' ').map(part => part.toLowerCase().startsWith($this.search.toLowerCase())).includes(true)) ||
            (booking.customer.lastName.split(' ').map(part => part.toLowerCase().startsWith($this.search.toLowerCase())).includes(true))
          )
        })

        this.hasResult = this.bookings.filter(booking => booking.show === true).length > 0

        if (this.hasResult) {
          this.activeTab = this.bookings.filter(booking => booking.show === true)[0].status === 'waiting' ? 'WaitingList' : 'Approved'
        }
      },

      openExportAttendeesDialog () {
        this.$emit('openExportAttendeesDialog')
      }
    },

    mounted () {
      this.instantiateDialog()
    },

    watch: {
      'bookingCreatedCount' () {
        this.bookings = this.eventBookings

        this.bookings.sort(function (a, b) {
          return (a.customer.firstName + ' ' + a.customer.lastName).localeCompare((b.customer.firstName + ' ' + b.customer.lastName))
        })

        this.hasResult = true
        this.search = ''
      }
    }
  }
</script>

© 2026 GrazzMean-Shell