FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
ameliabooking
/
assets
/
views
/
backend
/
dashboard
Edit File: Dashboard.vue
<template> <div class="am-wrap"> <get-premium-banner></get-premium-banner> <div id="am-dashboard" class="am-body"> <!-- Page Header --> <page-header @changeFilter="changeFilter" :params="params"></page-header> <!-- Spinner --> <div class="am-spinner am-section" v-show="!fetched || !fetchedStats"> <img :src="$root.getUrl + 'public/img/spinner.svg'"/> </div> <!-- Statistics --> <div v-if="fetched === true && fetchedStats === true"> <div class="am-hello am-section"> <div class="am-user-name"> <h1 v-if="currentUser !== null">{{$root.labels.hello_message_part0}} {{currentUser.firstName}} {{currentUser.lastName}} <img :src="$root.getUrl + 'public/img/wave.png'"></h1> <div class="am-user-alert"> <span>{{$root.labels.hello_message_part1}} <img :src="$root.getUrl + 'public/img/check.png'"> <span>{{todayAppointmentsCount.approved !== null ? todayAppointmentsCount.approved : 0}}</span> {{$root.labels.approved_appointments.toLowerCase()}} {{$root.labels.hello_message_part2}} <img :src="$root.getUrl + 'public/img/clock.png'"> {{todayAppointmentsCount.pending !== null ? todayAppointmentsCount.pending : 0}} {{$root.labels.pending_appointments.toLowerCase()}} {{$root.labels.hello_message_part3}}</span> </div> </div> </div> <div class="am-stats am-section"> <div class="am-big-stats"> <el-row :gutter="0"> <el-col :sm="24" :md="12" :lg="8"> <div class="am-grid-content"> <div class="am-title"> <h3>{{$root.labels.approved_appointments}} <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.approved_appointments_tooltip"></div> <i class="el-icon-question am-tooltip-icon"></i> </el-tooltip> <span class="am-change" :class="countGrowthClass">{{ selectedPeriodStats.count - previousPeriodStats.count }}</span> </h3> </div> <div class="am-big-num"> <span>{{calculateChartTotal('count')}}</span> </div> <!-- Small Chart --> <bar-chart ref="appointmentsCountChart" :data="smallBarChartAppointmentsData" :options="smallBarChartAppointmentsOptions" :width=40 :height=15 > </bar-chart> <div> <a class="am-goto" @click="navigateTo('appointments')">{{ $root.labels.view }} {{$root.labels.appointments}}</a> </div> </div> </el-col> <el-col :sm="24" :md="12" :lg="8"> <div class="am-grid-content"> <div class="am-title"> <h3>{{$root.labels.percentage_of_load}} <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.percentage_of_load_tooltip"></div> <i class="el-icon-question am-tooltip-icon"></i> </el-tooltip> <span class="am-change" :class="loadGrowthClass">{{ loadGrowthPercentage }}{{ loadGrowthPercentageCharacter }}</span> </h3> </div> <div class="am-big-num"> <span>{{calculateChartTotal('load')}}</span> </div> <line-chart ref="appointmentsLoadChart" :data="smallLineChartLoadData" :options="smallLineChartLoadOptions" :width=40 :height=15 > </line-chart> <div v-if="!$root.licence.isLite"> <a class="am-goto" @click="navigateTo('employees')">{{ $root.labels.view }} {{$root.labels.employees}}</a> </div> </div> </el-col> <el-col :sm="24" :md="12" :lg="8"> <div class="am-grid-content"> <div class="am-title"> <h3>{{$root.labels.revenue}} <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.revenue_tooltip"></div> <i class="el-icon-question am-tooltip-icon"></i> </el-tooltip> <span class="am-change" :class="revenueGrowthClass">{{ revenueGrowthPercentage }}{{ revenueGrowthPercentageCharacter }}</span> </h3> </div> <div class="am-big-num"> <span>{{calculateChartTotal('revenue')}}</span> </div> <line-chart ref="appointmentsRevenueChart" :data="smallLineChartRevenueData" :options="smallLineChartRevenueOptions" :width=40 :height=15 > </line-chart> <div> <a class="am-goto" @click="navigateTo('finance')">{{ $root.labels.view }} {{$root.labels.finance}}</a> </div> </div> </el-col> </el-row> </div> </div> <!-- Employee Stats--> <div class="am-employee-table-stats am-section"> <el-tabs v-model="tableStats"> <el-tab-pane :label="$root.labels.employees" name="employeeTableStats"> <el-table :data="visibleEmployeeTableData" :default-sort = "{prop: 'employeeName', order: 'ascending'}" style="width: 100%;" :empty-text="$root.labels.no_employees_yet" @sort-change="employeeTableSortChange"> <el-table-column fixed prop="employeeName" :label="$root.labels.employee" sortable min-width="180" > <template slot-scope="scope"> <img :src="pictureLoad(scope.row.provider, true)" @error="imageLoadError(scope.row.provider, true)" /> {{ scope.row.employeeName }} </template> </el-table-column> <el-table-column prop="numAppointments" :label="$root.labels.appointments_count" sortable min-width="220" > </el-table-column> <el-table-column prop="sumPayments" :label="$root.labels.appointments_revenue" :formatter="revenueFormatter" sortable min-width="220" > </el-table-column> <el-table-column prop="hoursAppointment" :label="$root.labels.appointments_hours" :formatter="hoursFormatter" sortable min-width="220" > </el-table-column> <el-table-column prop="load" :label="$root.labels.appointments_load" sortable min-width="220" > <template slot-scope="scope"> <div style="width: 100%;"> <div style="width: 50px; display: inline-block;">{{ scope.row.load }}%</div> <el-progress :width="120" :show-text=false :percentage=scope.row.load :color=getPercentageBarColor(scope.row.load)> </el-progress> </div> </template> </el-table-column> </el-table> <!-- Pagination --> <pagination-block :params="employeeTableParams" :show="employeeTableParams.show" :count="employeeTableParams.total" :label="$root.labels.employees.toLowerCase()" :visible="employeeTableParams.show < employeeTableParams.total" @change="changeVisibleEmployeeTableData" > </pagination-block> </el-tab-pane> <el-tab-pane :label="$root.labels.services" name="serviceTableStats"> <el-table :data="visibleServiceTableData" :default-sort = "{prop: 'serviceName', order: 'ascending'}" style="width: 100%;" :empty-text="$root.labels.no_services_yet" @sort-change="serviceTableSortChange"> <el-table-column fixed prop="serviceName" :label="$root.labels.service" sortable min-width="180" > <template slot-scope="scope"> <img :src="pictureLoad(scope.row.service, false)" @error="imageLoadError(scope.row.service, false)" /> {{ scope.row.serviceName }} </template> </el-table-column> <el-table-column prop="numAppointments" :label="$root.labels.appointments_count" sortable min-width="220" > </el-table-column> <el-table-column prop="sumPayments" :label="$root.labels.appointments_revenue" :formatter="revenueFormatter" sortable min-width="220" > </el-table-column> <el-table-column prop="hoursAppointment" :label="$root.labels.appointments_hours" :formatter="hoursFormatter" sortable min-width="220" > </el-table-column> <el-table-column prop="load" :label="$root.labels.appointments_load" sortable min-width="220" > <template slot-scope="scope"> <div style="width: 100%"> <span>{{ scope.row.load }}%</span> <el-progress :width="120" :show-text=false :percentage=scope.row.load :color=getPercentageBarColor(scope.row.load)> </el-progress> </div> </template> </el-table-column> </el-table> <!-- Pagination --> <pagination-block :params="serviceTableParams" :show="serviceTableParams.show" :count="serviceTableParams.total" :label="$root.labels.services.toLowerCase()" :visible="serviceTableParams.show < serviceTableParams.total" @change="changeVisibleServiceTableData" > </pagination-block> </el-tab-pane> <el-tab-pane :label="$root.labels.packages" name="packageTableStats" class="am-packages-feature" v-if="$root.licence.isPro || $root.licence.isDeveloper"> <el-table :data="visiblePackageTableData" :default-sort = "{prop: 'packageName', order: 'ascending'}" style="width: 100%;" :empty-text="$root.labels.no_packages_yet" @sort-change="packageTableSortChange"> <el-table-column fixed prop="packageName" :label="$root.labels.package" sortable min-width="180" > <template slot-scope="scope"> <img :src="pictureLoad(scope.row.pack, false)" @error="imageLoadError(scope.row.pack, false)" /> {{ scope.row.packageName }} </template> </el-table-column> <el-table-column prop="numPurchased" :label="$root.labels.packages_purchased_count" sortable min-width="220" > </el-table-column> <el-table-column prop="sumPayments" :label="$root.labels.appointments_revenue" :formatter="revenueFormatter" sortable min-width="220" > </el-table-column> <el-table-column prop="hoursAppointment" :label="$root.labels.appointments_hours" :formatter="hoursFormatter" sortable min-width="220" > </el-table-column> </el-table> <!-- Pagination --> <pagination-block :params="packageTableParams" :show="packageTableParams.show" :count="packageTableParams.total" :label="$root.labels.packages.toLowerCase()" :visible="packageTableParams.show < packageTableParams.total" @change="changeVisiblePackageTableData" > </pagination-block> </el-tab-pane> </el-tabs> </div> <!-- Upcoming Appointments --> <div id="am-appointments" class="am-upcoming-appointments am-section"> <!-- Header --> <el-form :model="params" class="demo-form-inline" :action="exportAction" method="POST"> <el-row> <!-- Header Title --> <el-col :span="20"> <h2 class="am-section-title">{{ $root.labels.upcoming_appointments }}</h2> </el-col> <!-- Export Button --> <el-col :span="4"> <div class="align-right"> <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.export_tooltip_appointments"></div> <el-button class="button-export am-button-icon" :disabled="appointments.length === 0" @click="dialogExport = true" > <img class="svg-amelia" :alt="$root.labels.export" :src="$root.getUrl+'public/img/export.svg'"/> </el-button> </el-tooltip> </div> </el-col> </el-row> <!-- Dialog Export --> <transition name="slide"> <el-dialog :close-on-click-modal="false" class="am-side-dialog am-dialog-export" :visible.sync="dialogExport" :show-close="false" v-if="dialogExport" > <dialog-export :data="getExportParams()" :action="$root.getAjaxUrl + '/report/appointments'" @updateAction="(action) => {this.exportAction = action}" @closeDialogExport="dialogExport = false" > </dialog-export> </el-dialog> </transition> </el-form> <!-- Appointments List Head --> <div class="am-appointments-list-head" v-if="appointments.length > 0"> <el-row> <el-col :lg="15"> <el-row :gutter="10" class="am-appointments-flex-row-middle-align"> <el-col :lg="5" :md="5"> <p>{{ $root.labels.date }} / {{ $root.labels.time }}:</p> </el-col> <el-col :lg="5" :md="5"> <p>{{ $root.labels.customer }}:</p> </el-col> <el-col :lg="5" :md="5"> <p>{{ $root.labels.assigned_to }}:</p> </el-col> <el-col :lg="9" :md="9"> <p>{{ $root.labels.service }}:</p> </el-col> </el-row> </el-col> <el-col :lg="9"> <el-row :gutter="10" class="am-appointments-flex-row-middle-align"> <el-col :lg="0" :md="3"></el-col> <el-col :lg="5" :md="6"> <p>{{ $root.labels.duration }}:</p> </el-col> <el-col :lg="6" :md="6"> <p>{{ $root.labels.price }}:</p> </el-col> <el-col :lg="13" :md="6"> <p>{{ $root.labels.status }}:</p> </el-col> </el-row> </el-col> </el-row> </div> <!-- Appointments List --> <div v-if="appointments.length > 0" class="am-appointments" > <div class="am-appointments-list"> <el-collapse> <el-collapse-item v-for="app in appointments" :key="app.id" :name="app.id" class="am-appointment" :class="appointmentCameFrom(app)" > <template slot="title"> <div class="am-appointment-data"> <el-row> <el-col :lg="15"> <el-row :gutter="10" class="am-appointments-flex-row-middle-align"> <!-- Appointment Time --> <el-col :lg="5" :sm="5"> <span class="am-appointment-time" :class="app.status">{{ getFrontedFormattedDateTime(app.bookingStart) }}</span> </el-col> <!-- Appointment Customer(s) --> <el-col :lg="5" :sm="6"> <p class="am-col-title">{{ $root.labels.customer }}:</p> <template> <el-tooltip class="item" effect="dark" placement="top" :disabled="app.bookings.length === 1" popper-class="am-align-left" > <div v-if="app.bookings.length > 1" slot="content" v-html="getCustomersFromGroup(app)" ></div> <h3 :class="{ grouped: app.bookings.length > 1 }"> <img v-show="app.bookings.length > 1" width="16px" :src="$root.getUrl+'public/img/group.svg'" class="svg-amelia" /> <span v-for="(booking, index) in app.bookings" :class="(app.bookings.length === 1 ? getNoShowClass(booking.customerId, customersNoShowCount, null, booking.customer.status) : '')"> {{ ((user = getCustomerInfo(booking)) !== null ? user.firstName + ' ' + user.lastName : '') }}<span v-if="app.bookings.length > 1 && index + 1 !== app.bookings.length">,</span> </span> </h3> </el-tooltip> <span v-if="app.bookings.length === 1" v-for="booking in app.bookings">{{ ((user = getCustomerById(booking.customerId)) !== null ? user.email : '') }}</span> <span v-if="app.bookings.length > 1">{{$root.labels.multiple_emails}}</span> </template> </el-col> <!-- Appointment Provider --> <el-col :lg="5" :sm="6"> <p class="am-col-title">{{ $root.labels.assigned }}:</p> <div class="am-assigned"> <img :src="pictureLoad(getProviderById(app.providerId), true)" @error="imageLoadError(getProviderById(app.providerId), true)" v-if="options.fetched"/> <h4> {{ ((user = getProviderById(app.providerId)) !== null ? user.firstName + ' ' + user.lastName : '') }} </h4> </div> </el-col> <!-- Appointment Service --> <el-col :lg="9" :sm="7"> <p class="am-col-title">{{ $root.labels.service }}:</p> <h4> {{ ((service = getServiceById(app.serviceId)) !== null ? service.name : '') }} </h4> </el-col> </el-row> </el-col> <el-col :lg="9"> <el-row :gutter="10" class="am-appointments-flex-row-middle-align"> <!-- Appointment Duration --> <el-col :lg="5" :sm="5" :xs="12"> <p class="am-col-title">{{ $root.labels.duration }}:</p> <h4>{{ momentDurationToNiceDurationWithUnit(convertDateTimeRangeDifferenceToMomentDuration(app.bookingStart, app.bookingEnd)) }}</h4> </el-col> <!-- Appointment Payment --> <el-col class="am-appointment-payment" :lg="6" :sm="6" :xs="12"> <p class="am-col-title">{{ $root.labels.price }}:</p> <div class="am-appointment-package-wrap" v-if="getAppointmentPaymentMethods(app.bookings).length"> <h4> <el-tooltip placement="top" effect="light"> <div slot="content" class="am-appointment-payment-tooltip"> <span style="margin-bottom: 0; vertical-align: middle">{{ $root.labels.payment_method }}:</span> <img v-for="method in getAppointmentPaymentMethods(app.bookings)" v-if="getAppointmentPaymentMethods(app.bookings).length && method" :src="$root.getUrl + 'public/img/payments/' + method + '.svg'" height="16px" style="margin-left: 5px; vertical-align: middle" > </div> <div class="am-appointment-payment-wrap"> <img v-for="method in getAppointmentPayment(app.bookings)" v-if="getAppointmentPayment(app.bookings).length" :src="$root.getUrl + 'public/img/payments/icons/' + getPaymentType(method) + '.svg'" class="am-appointment-payment-icons" > <span v-if="bookingTypeCountInPackage(app.bookings).regular" class="am-appointment-payment-wrap-price"> <span style="vertical-align: middle"> {{ getAppointmentPrice(getAppointmentService(app), app.bookings) }} </span> <span v-if="Object.keys(bookingTypeCountInPackage(app.bookings).package).length">+</span> </span> </div> </el-tooltip> </h4> <el-tooltip v-if="Object.keys(bookingTypeCountInPackage(app.bookings).package).length" placement="top" :content="$root.labels.bookings_payment_package_tooltip" effect="light" > <img v-if="Object.keys(bookingTypeCountInPackage(app.bookings).package).length" :src="$root.getUrl + 'public/img/am-package.svg'" > </el-tooltip> </div> </el-col> <!-- Appointment Status --> <el-col :lg="8" :sm="8" :xs="17"> <div class="am-appointment-status" @click.stop> <span :class="'am-appointment-status-symbol am-appointment-status-symbol-'+app.status"></span> <el-select v-model="app.status" :placeholder="$root.labels.status" @change="updateAppointmentStatus(app, app.status, false)" :disabled="app.past" > <el-option v-for="opt in statuses" :key="opt.value" :label="opt.label" :value="opt.value" class="am-appointment-status-option" > <span :class="'am-appointment-status-symbol am-appointment-status-symbol-'+opt.value">{{ opt.label }}</span> </el-option> </el-select> </div> </el-col> <!-- Appointment Edit --> <el-col :lg="5" :sm="5" :xs="7"> <div class="am-edit-btn" @click.stop> <el-button @click="showDialogEditAppointment(app.id)"> {{ $root.labels.edit }} </el-button> </div> </el-col> </el-row> </el-col> </el-row> </div> </template> <appointment-list-collapsed :app="app" :options="options" :customersNoShowCount="customersNoShowCount" > </appointment-list-collapsed> </el-collapse-item> </el-collapse> </div> </div> <!-- No Results --> <div class="am-empty-state am-section" v-if="appointments.length === 0"> <img :src="$root.getUrl + 'public/img/emptystate.svg'"> <p>{{ $root.labels.no_upcoming_appointments }}</p> </div> </div> <!-- Charts --> <div class="am-charts am-section"> <el-row :gutter="32"> <!-- Conversions Charts --> <el-col :md="16" class="am-border-right"> <div class="am-chart bar-chart"> <h2 class="am-section-title"> {{ $root.labels.conversions }} <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.conversions_tooltip"></div> <i class="el-icon-question am-tooltip-icon"></i> </el-tooltip> </h2> <el-tabs v-model="chartTabs"> <!-- Employees Conversions Chart Tab --> <el-tab-pane :label="$root.labels.employees" name="employee"> <!-- Employees Conversions Chart Filter --> <div class="am-chart-filter"> <el-row :gutter="10"> <el-col :sm="12"> <el-select v-model="employees" @change="filterEmployeesChart" filterable clearable :placeholder="$root.labels.select_employee" multiple collapse-tags > <el-option v-for="item in options.entities.employees" :key="item.id" :label="item.firstName + ' ' + item.lastName" :value="item.id" > </el-option> </el-select> </el-col> </el-row> </div> <!-- Employees Conversions Chart --> <bar-chart v-if="chartTabs === 'employee'" ref="employeesChart" :data="employeesChartData" :options="defaultBarChartOptions" > </bar-chart> </el-tab-pane> <!-- Services Conversions Chart Tab --> <el-tab-pane :label="$root.labels.services" name="service"> <!-- Services Conversions Chart Filter --> <div class="am-chart-filter"> <el-row :gutter="10"> <el-col :sm="12"> <el-select v-model="services" @change="filterServicesChart" filterable clearable :placeholder="$root.labels.select_service" multiple collapse-tags > <el-option v-for="item in options.entities.services" :key="item.id" :label="item.name" :value="item.id" > </el-option> </el-select> </el-col> </el-row> </div> <!-- Services Conversions Chart --> <bar-chart v-if="chartTabs === 'service'" ref="servicesChart" :data="servicesChartData" :options="defaultBarChartOptions" > </bar-chart> </el-tab-pane> <!-- Locations Conversions Chart Tab --> <el-tab-pane :label="$root.labels.locations" name="location" v-if="options.entities.locations.length"> <!-- Locations Conversions Chart Filter --> <div class="am-chart-filter"> <el-row :gutter="10"> <el-col :sm="12"> <el-select v-model="locations" @change="filterLocationsChart" filterable clearable :placeholder="$root.labels.select_location" multiple collapse-tags > <el-option v-for="item in options.entities.locations" :key="item.id" :label="item.name" :value="item.id" > </el-option> </el-select> </el-col> </el-row> </div> <!-- Locations Conversions Chart --> <bar-chart v-if="chartTabs === 'location'" ref="locationsChart" :data="locationsChartData" :options="defaultBarChartOptions" > </bar-chart> </el-tab-pane> </el-tabs> </div> </el-col> <!-- Customers Chart --> <el-col :md="8"> <div class="am-chart doughnut-chart"> <!-- Customers Label and Growth Stats --> <el-row> <el-col :span="12"> <h2 class="am-section-title"> {{ $root.labels.customers }} <el-tooltip placement="top"> <div slot="content" v-html="$root.labels.customers_tooltip"></div> <i class="el-icon-question am-tooltip-icon"></i> </el-tooltip> </h2> </el-col> <el-col :span="12"> <h2 class="align-right" v-if="fetched">{{ totalCustomers }} <span :class="customerGrowthClass"> {{ customersGrowthPercentage }}{{ customerGrowthPercentageCharacter }} </span> </h2> </el-col> </el-row> <!-- Customers Chart --> <div class="" style="padding: 0 40px;"> <doughnut-chart ref="customersChart" :data="customersChartData" > </doughnut-chart> </div> <!-- Customers Progress Charts --> <el-row> <el-col :span="12"> <p class="am-big-num" v-if="fetched"> {{ newCustomers }} </p> <p>{{ $root.labels.new }}</p> <el-progress v-if="fetched" :percentage="newCustomersPercentage" color="#1A84EE" > </el-progress> </el-col> <el-col :span="12"> <p class="am-big-num" v-if="fetched"> {{ returningCustomers }} </p> <p>{{ $root.labels.returning }}</p> <el-progress v-if="fetched" :percentage="returnedCustomersPercentage" color="#FFD400" > </el-progress> </el-col> </el-row> </div> </el-col> </el-row> </div> <!-- Button New --> <div v-if="$root.settings.capabilities.canWrite === true" id="am-button-new" class="am-button-new"> <el-popover ref="popover" placement="top" width="160" v-model="popover" visible-arrow="false" popper-class="am-button-popover"> <div class="am-overlay" @click="popover = false; buttonNewItems = !buttonNewItems"> <div class="am-button-new-items"> <transition name="el-zoom-in-bottom"> <div v-show="buttonNewItems"> <el-button @click="showDialogNewAppointment()">{{ $root.labels.new_appointment }}</el-button> <el-button @click="showDialogNewCustomer">{{ $root.labels.create_customer }}</el-button> </div> </transition> </div> </div> </el-popover> <el-button id="am-plus-symbol" v-popover:popover type="primary" icon="el-icon-plus" @click="buttonNewItems = !buttonNewItems" ref="rotating" > </el-button> </div> <!-- 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 :close-on-click-modal="false" :appointment="appointment" :recurringAppointments="recurringAppointments" :savedAppointment="savedAppointment" :bookings="bookings" :options="options" :packageCustomer="null" :customersNoShowCount="customersNoShowCount" @sortBookings="sortBookings" @saveCallback="getDashboardOptions" @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" :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> </div> <!-- <transition name="fade"> <div class="am-amelia-banner"> <div class="am-amelia-banner__content"> <div class="am-amelia-banner__content-main"> <div class="am-amelia-banner__content-main-left"> <div class="am-amelia-banner__content-main-left-logo"> <img :src="$root.getUrl + 'public/img/promo/wpDataTables-logo-with-name.svg'"/> </div> <div class="am-amelia-banner__content-main-left-subtitle"> <p>wpDataTables {{ $root.licence.isLite ? 'Lite' : 'Premium' }}</p> </div> <div class="am-amelia-banner__content-main-left-text"> <p>The most powerful WordPress table plugin - wpDataTables is designed to make the process of data representation and interaction quick, easy and effective.</p> </div> <div class="am-amelia-banner__content-main-left-rating"> <img :src="$root.getUrl + 'public/img/promo/Rating.svg'"/> <p class="am-amelia-banner__content-main-left-rating-text"> Rating: 4.8 - 263 reviews </p> </div> <div class="am-amelia-banner__content-main-left-buttons"> <a v-if="$root.licence.isLite" class="am-promo-btn el-button el-button--primary" href="https://downloads.wordpress.org/plugin/wpdatatables.zip" > <span>Free Download</span> <img :src="$root.getUrl + 'public/img/promo/download.svg'"/> </a> <a class="am-promo-btn el-button el-button--primary" :href="$root.licence.isLite ? 'https://wordpress.org/plugins/wpdatatables/' : 'https://wpdatatables.com/'" target="_blank" > <span>Learn More</span> <img :src="$root.getUrl + 'public/img/promo/arrow-right.svg'"/> </a> </div> </div> <div class="am-amelia-banner__content-main-right"> <img :src="$root.getUrl + 'public/img/promo/wpdt-promo.webp'"/> </div> </div> </div> </div> </transition>--> <!-- Help Button --> <el-col :md="6" class=""> <a class="am-help-button" href="https://wpamelia.com/admin-dashboard/" target="_blank" rel="nofollow"> <i class="el-icon-question"></i> {{ $root.labels.need_help }}? </a> </el-col> </div> <dialog-new-customize v-if="false"></dialog-new-customize> </div> </template> <script> import AppointmentListCollapsed from '../appointments/AppointmentListCollapsed.vue' import appointmentMixin from '../../../js/backend/mixins/appointmentMixin' import appointmentPriceMixin from '../../../js/backend/mixins/appointmentPriceMixin' import BarChart from '../../../js/backend/components/barchart' import DoughnutChart from '../../../js/backend/components/doughnutchart' import LineChart from '../../../js/backend/components/linechart' 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 DialogExport from '../parts/DialogExport.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 priceMixin from '../../../js/common/mixins/priceMixin' import PaginationBlock from '../parts/PaginationBlock.vue' import DialogNewCustomize from '../parts/DialogNewCustomize.vue' import GetPremiumBanner from '../parts/GetPremiumBanner.vue' export default { mixins: [paymentMixin, entitiesMixin, appointmentMixin, imageMixin, dateMixin, durationMixin, priceMixin, customerMixin, notifyMixin, appointmentPriceMixin], data () { return { customersNoShowCount: [], currentUser: null, todayAppointmentsCount: { approved: null, pending: null }, periodChange: { count: 0, available: 0, occupied: 0, revenue: 0 }, previousPeriodStats: { count: 0, available: 0, occupied: 0, revenue: 0 }, selectedPeriodStats: { count: 0, available: 0, occupied: 0, revenue: 0 }, statsLabels: [], customer: null, appointments: [], appointmentsCount: [], buttonNewItems: false, chartTabs: 'employee', customersChartData: { labels: [this.$root.labels.new, this.$root.labels.returning, ''], datasets: [ { backgroundColor: ['#1a84ee', '#ffd400', '#ebeef5'], borderColor: '#E2E6EC', data: [0, 0, 1], hoverBackgroundColor: ['#117ce6', '#eec600', '#ebeef5'], hoverBorderColor: '#D3DDEA' } ] }, dialogAppointment: false, dialogPayment: false, dialogExport: false, employees: [], tableStats: 'employeeTableStats', smallBarChartAppointmentsData: { labels: [], datasets: [ { backgroundColor: '#5FCE19', data: [], hoverBackgroundColor: '#5FCE19', label: '', borderWidth: 0 } ] }, smallBarChartAppointmentsOptions: { legend: { display: false }, scales: { xAxes: [{ barThickness: 6, beginAtZero: true, gridLines: { display: false }, ticks: { stepSize: 1, min: 1, autoSkip: true } }], yAxes: [{ display: false, beginAtZero: true, gridLines: { display: false }, ticks: { stepSize: 10, min: 0 } }] }, tooltips: { custom: function (tooltip) { if (!tooltip) { return } tooltip.displayColors = false }, callbacks: { label: (tooltipItems, data) => { return this.statsLabels[tooltipItems.xLabel] + ': ' + tooltipItems.yLabel }, title: (tooltipItems, data) => { } } } }, smallLineChartLoadData: { labels: [], datasets: [ { backgroundColor: 'transparent', borderColor: '#9A47FF', data: [], label: '', borderWidth: 2, lineTension: 0, pointRadius: 3, pointBorderColor: '#fff' } ] }, smallLineChartLoadOptions: { legend: { display: false }, scales: { xAxes: [{ gridLines: { display: false }, ticks: { stepSize: 10, min: 0, autoSkip: true } }], yAxes: [{ display: false, beginAtZero: true, gridLines: { display: false }, ticks: { stepSize: 1, min: 0 } }] }, tooltips: { custom: function (tooltip) { if (!tooltip) { return } tooltip.displayColors = false }, callbacks: { label: (tooltipItems, data) => { return this.statsLabels[tooltipItems.xLabel] + ': ' + tooltipItems.yLabel + '%' }, title: (tooltipItems, data) => { } } } }, smallLineChartRevenueData: { labels: [], datasets: [ { backgroundColor: 'transparent', borderColor: '#FD8863', data: [], label: '', borderWidth: 2, lineTension: 0, pointBackgroundColor: '#FD8863', pointRadius: 3, pointBorderColor: '#fff' } ] }, smallLineChartRevenueOptions: { legend: { display: false }, scales: { xAxes: [{ barPercentage: 0.2, categoryPercentage: 0.8, gridLines: { display: false }, ticks: { stepSize: 1, min: 0, autoSkip: true } }], yAxes: [{ display: false, beginAtZero: true, gridLines: { display: false }, ticks: { stepSize: 100, min: 0 } }] }, tooltips: { custom: function (tooltip) { if (!tooltip) { return } tooltip.displayColors = false }, callbacks: { label: (tooltipItems, data) => { return this.statsLabels[tooltipItems.xLabel] + ': ' + this.getFormattedPrice(tooltipItems.yLabel) }, title: (tooltipItems, data) => { } } } }, employeePeriodStats: [], employeeTableData: [], visibleEmployeeTableData: [], serviceTableData: [], visibleServiceTableData: [], packageTableData: [], visiblePackageTableData: [], visibleTableDataCount: [], employeeTableParams: { show: 5, total: 0, page: 1 }, serviceTableParams: { show: 5, total: 0, page: 1 }, packageTableParams: { show: 5, total: 0, page: 1 }, employeesChartData: { labels: [], datasets: [ { backgroundColor: '#D3DDEA', data: [], hoverBackgroundColor: '#c8d4e5', label: this.$root.labels.views, borderWidth: 0 }, { backgroundColor: '#5FCE19', data: [], hoverBackgroundColor: '#58BF17', label: this.$root.labels.appointments, borderWidth: 0 } ] }, defaultBarChartOptions: { responsive: true, maintainAspectRatio: false, scales: { xAxes: [{ barPercentage: 0.5, categoryPercentage: 0.8, ticks: { stepSize: 1, min: 0, autoSkip: false } }], yAxes: [{ gridLines: { display: true }, ticks: { beginAtZero: true, userCallback: function (label) { if (Math.floor(label) === label) { return label } } } }] } }, employeesStats: [], fetched: false, fetchedStats: false, form: new Form(), locations: [], locationsChartData: { labels: [], datasets: [ { backgroundColor: '#D3DDEA', data: [], hoverBackgroundColor: '#c8d4e5', label: this.$root.labels.views, borderWidth: 0 }, { backgroundColor: '#5FCE19', data: [], hoverBackgroundColor: '#58BF17', label: this.$root.labels.appointments, borderWidth: 0 } ] }, locationsStats: [], params: { dates: this.getDatePickerInitRange() }, popover: false, selectedPaymentModalData: null, services: [], servicesChartData: { labels: [], datasets: [ { backgroundColor: '#D3DDEA', data: [], hoverBackgroundColor: '#c8d4e5', label: this.$root.labels.views, borderWidth: 0 }, { backgroundColor: '#5FCE19', data: [], hoverBackgroundColor: '#58BF17', label: this.$root.labels.appointments, borderWidth: 0 } ] }, totalPastPeriodCustomers: 0 } }, created () { Form.defaults.axios = this.$http this.getDashboardOptions() this.getCurrentUser() }, methods: { revenueFormatter (row, column) { return this.getFormattedPrice(row.sumPayments) }, hoursFormatter (row, column) { let hours = this.getMinutesToDays(row.hoursAppointment) return hours === '' ? 0 : hours }, employeeTableSortChange (sortProps) { switch (sortProps.order) { case (null): this.employeeTableData = this.employeeTableData.sort((a, b) => (a.employeeName > b.employeeName) ? 1 : -1) break case ('ascending'): this.employeeTableData = this.employeeTableData.sort((a, b) => (a[sortProps.prop] > b[sortProps.prop]) ? 1 : -1) break case ('descending'): this.employeeTableData = this.employeeTableData.sort((a, b) => (a[sortProps.prop] < b[sortProps.prop]) ? 1 : -1) break } this.showVisibleEmployeeTableData() }, serviceTableSortChange (sortProps) { switch (sortProps.order) { case (null): this.serviceTableData = this.serviceTableData.sort((a, b) => (a.serviceName > b.serviceName) ? 1 : -1) break case ('ascending'): this.serviceTableData = this.serviceTableData.sort((a, b) => (a[sortProps.prop] > b[sortProps.prop]) ? 1 : -1) break case ('descending'): this.serviceTableData = this.serviceTableData.sort((a, b) => (a[sortProps.prop] < b[sortProps.prop]) ? 1 : -1) break } this.showVisibleServiceTableData() }, packageTableSortChange (sortProps) { switch (sortProps.order) { case (null): this.packageTableData = this.packageTableData.sort((a, b) => (a.serviceName > b.serviceName) ? 1 : -1) break case ('ascending'): this.packageTableData = this.packageTableData.sort((a, b) => (a[sortProps.prop] > b[sortProps.prop]) ? 1 : -1) break case ('descending'): this.packageTableData = this.packageTableData.sort((a, b) => (a[sortProps.prop] < b[sortProps.prop]) ? 1 : -1) break } this.showVisiblePackageTableData() }, changeVisibleEmployeeTableData () { this.showVisibleEmployeeTableData() }, changeVisibleServiceTableData () { this.showVisibleServiceTableData() }, changeVisiblePackageTableData () { this.showVisiblePackageTableData() }, showVisibleEmployeeTableData () { this.visibleEmployeeTableData = this.employeeTableData.slice( (this.employeeTableParams.page - 1) * this.employeeTableParams.show, (this.employeeTableParams.page - 1) * this.employeeTableParams.show + this.employeeTableParams.show ) }, showVisibleServiceTableData () { this.visibleServiceTableData = this.serviceTableData.slice( (this.serviceTableParams.page - 1) * this.serviceTableParams.show, (this.serviceTableParams.page - 1) * this.serviceTableParams.show + this.serviceTableParams.show ) }, showVisiblePackageTableData () { this.visiblePackageTableData = this.packageTableData.slice( (this.packageTableParams.page - 1) * this.packageTableParams.show, (this.packageTableParams.page - 1) * this.packageTableParams.show + this.packageTableParams.show ) }, getExportParams () { return Object.assign({count: this.appointmentsCount, dates: {start: moment().format('YYYY-MM-DD'), end: ''}}, this.exportParams) }, showDialogNewCustomer () { this.customer = this.getInitCustomerObject() this.dialogCustomer = true }, getDashboardOptions () { this.fetchEntities((success) => { if (success) { this.setInitialCustomers() this.setBookings(0) this.getDashboard() } this.fetched = true this.options.fetched = true }, { types: ['locations', 'employees', 'categories', 'custom_fields', 'packages', 'resources', 'coupons'], page: 'appointments', isFrontEnd: false, isPanel: false }) }, changeFilter () { this.setDatePickerSelectedDaysCount(this.params.dates.start, this.params.dates.end) this.getDashboard() }, getDashboard () { let params = JSON.parse(JSON.stringify(this.params)) let dates = [] if (params.dates) { if (params.dates.start) { dates.push(moment(params.dates.start).format('YYYY-MM-DD')) } if (params.dates.end) { dates.push(moment(params.dates.end).format('YYYY-MM-DD')) } params.dates = dates } this.fetchedStats = false this.$http.get(`${this.$root.getAjaxUrl}/stats`, { params: this.getAppropriateUrlParams(params) }) .then(response => { this.employeePeriodStats = response.data.data.selectedPeriodStats this.previousPeriodStats = this.getPeriodStats(response.data.data.previousPeriodStats, 'providers') this.selectedPeriodStats = this.getPeriodStats(this.employeePeriodStats, 'providers') this.fillAppointmentsChartStats(response.data.data.selectedPeriodStats) this.fillAppointmentsTablesStats(this.employeePeriodStats) this.todayAppointmentsCount.approved = response.data.data.count.approved this.todayAppointmentsCount.pending = response.data.data.count.pending let customersIds = this.options.entities.customers.map(customer => parseInt(customer.id)) let customers = this.options.entities.customers response.data.data.appointments.forEach((app) => { 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) this.appointments = response.data.data.appointments this.appointmentsCount = response.data.data.appointmentsCount this.customersNoShowCount = response.data.data.customersNoShowCount this.fillCustomersChart(response.data.data.customersStats) this.employeesStats = response.data.data.employeesStats this.fillEmployeesChart(response.data.data.employeesStats) this.servicesStats = response.data.data.servicesStats this.fillServicesChart(response.data.data.servicesStats) this.locationsStats = response.data.data.locationsStats this.fillLocationsChart(response.data.data.locationsStats) this.updateCharts() this.filterEmployeesChart() this.filterServicesChart() this.filterLocationsChart() this.fetched = true this.fetchedStats = true }) }, appointmentCameFrom (app) { let arr = [] app.bookings.forEach(booking => { if (booking.info) { arr.push('front') } else { arr.push('back') } }) if (arr.indexOf('front') !== -1 && arr.indexOf('back') !== -1) { return 'am-mixed-appointment' } if (arr.indexOf('front') !== -1 && arr.indexOf('back') < 0) { return 'am-front-appointment' } if (arr.indexOf('front') < 0 && arr.indexOf('back') !== -1) { return 'am-back-appointment' } return '' }, navigateTo (pageName) { let startDate = moment(this.params.dates.start).format('YYYY-MM-DD') let endDate = moment(this.params.dates.end).format('YYYY-MM-DD') let url = 'admin.php?page=wpamelia-' + pageName switch (pageName) { case ('appointments'): url += '&dateFrom=' + startDate + '&dateTo=' + endDate + '&status=approved' break case ('finance'): url += '&dateFrom=' + startDate + '&dateTo=' + endDate + '&status=paid' break case ('employees'): break } window.location = url }, updateCharts () { if (typeof this.$refs.customersChart !== 'undefined') { this.$refs.customersChart.update(false) } if (typeof this.$refs.employeesChart !== 'undefined') { this.$refs.employeesChart.update(false) } if (typeof this.$refs.servicesChart !== 'undefined') { this.$refs.servicesChart.update(false) } if (typeof this.$refs.locationsChart !== 'undefined') { this.$refs.locationsChart.update(false) } if (typeof this.$refs.appointmentsCountChart !== 'undefined') { this.$refs.appointmentsCountChart.update(true) } if (typeof this.$refs.appointmentsLoadChart !== 'undefined') { this.$refs.appointmentsLoadChart.update(true) } if (typeof this.$refs.appointmentsRevenueChart !== 'undefined') { this.$refs.appointmentsRevenueChart.update(true) } }, getPeriodStats (data, type) { let appointmentsCount = 0 let appointmentsAvailable = 0 let appointmentsOccupied = 0 let appointmentsRevenue = 0 for (let dateKey in data) { if (!data.hasOwnProperty(dateKey) || data[dateKey] === null || !data[dateKey].hasOwnProperty(type)) { continue } for (let providerId in data[dateKey][type]) { if (!data[dateKey][type].hasOwnProperty(providerId)) { continue } let availableTime = 0 for (let i = 0; i < data[dateKey][type][providerId].intervals.length; i++) { availableTime += (parseInt(data[dateKey][type][providerId].intervals[i].time[1]) - parseInt(data[dateKey][type][providerId].intervals[i].time[0])) / 60 } appointmentsCount += parseInt(data[dateKey][type][providerId].count) appointmentsAvailable += availableTime appointmentsOccupied += parseInt(data[dateKey][type][providerId].occupied) appointmentsRevenue += parseFloat(data[dateKey][type][providerId].revenue) } } return { count: appointmentsCount, occupied: appointmentsOccupied, available: appointmentsAvailable, revenue: appointmentsRevenue } }, fillAppointmentsChartStats (data) { this.statsLabels = [] this.smallBarChartAppointmentsData.labels = [] this.smallBarChartAppointmentsData.datasets[0].data = [] this.smallLineChartLoadData.labels = [] this.smallLineChartLoadData.datasets[0].data = [] this.smallLineChartRevenueData.labels = [] this.smallLineChartRevenueData.datasets[0].data = [] let type = '' for (let dateKey in data) { if (!data.hasOwnProperty(dateKey) || data[dateKey] === null) { continue } let dateFormatted = moment(dateKey, 'YYYY-MM-DD').format('MMM D') this.statsLabels[dateFormatted] = moment(dateKey, 'YYYY-MM-DD').format('dddd') this.smallBarChartAppointmentsData.labels.push(dateFormatted) this.smallLineChartLoadData.labels.push(dateFormatted) this.smallLineChartRevenueData.labels.push(dateFormatted) let dateAppointmentsCount = 0 let dateAppointmentsAvailable = 0 let dateAppointmentsOccupied = 0 let dateRevenue = 0 let hasData = false type = 'providers' for (let providerId in data[dateKey][type]) { if (!data[dateKey][type].hasOwnProperty(providerId)) { continue } let availableTime = 0 for (let i = 0; i < data[dateKey][type][providerId].intervals.length; i++) { availableTime += (data[dateKey][type][providerId].intervals[i].time[1] - data[dateKey][type][providerId].intervals[i].time[0]) / 60 } dateAppointmentsCount += parseInt(data[dateKey][type][providerId].count) dateAppointmentsAvailable += availableTime dateAppointmentsOccupied += parseInt(data[dateKey][type][providerId].occupied) dateRevenue += parseFloat(data[dateKey][type][providerId].revenue) } if (dateAppointmentsAvailable > 0) { hasData = true } type = 'packages' for (let packageId in data[dateKey][type]) { if (!data[dateKey][type].hasOwnProperty(packageId)) { continue } dateRevenue += parseFloat(data[dateKey][type][packageId].revenue) } if (dateRevenue > 0) { hasData = true } if (!hasData) { this.smallBarChartAppointmentsData.datasets[0].data.push(null) this.smallLineChartLoadData.datasets[0].data.push(null) this.smallLineChartRevenueData.datasets[0].data.push(null) } else { this.smallBarChartAppointmentsData.datasets[0].data.push(dateAppointmentsCount) this.smallLineChartLoadData.datasets[0].data.push(dateAppointmentsAvailable > 0 ? parseFloat((dateAppointmentsOccupied / dateAppointmentsAvailable * 100).toFixed(1)) : 0) this.smallLineChartRevenueData.datasets[0].data.push(dateRevenue) } } }, fillAppointmentsTablesStats (data) { let employeeData = [] let serviceData = [] let packageData = [] let that = this this.options.entities.packages.forEach((pack) => { packageData[pack.id] = { count: 0, available: 0, occupied: 0, revenue: 0, purchased: 0 } }) for (let dateKey in data) { if (!data.hasOwnProperty(dateKey) || data[dateKey] === null) { continue } for (let serviceId in data[dateKey].services) { if (!data[dateKey].services.hasOwnProperty(serviceId)) { continue } let servicesStats = data[dateKey].services[serviceId] if (!(serviceId in serviceData)) { serviceData[serviceId] = { count: 0, available: 0, occupied: 0, revenue: 0 } } serviceData[serviceId].occupied += parseInt(servicesStats.occupied) serviceData[serviceId].count += parseInt(servicesStats.count) serviceData[serviceId].revenue += parseFloat(servicesStats.revenue) } for (let providerId in data[dateKey].providers) { if (!data[dateKey].providers.hasOwnProperty(providerId)) { continue } if (!(providerId in employeeData)) { employeeData[providerId] = { count: 0, available: 0, occupied: 0, revenue: 0 } } let providerAvailableTime = 0 let providerStats = data[dateKey].providers[providerId] let providerServices = that.options.entities.employees.find(employee => parseInt(employee.id) === parseInt(providerId)).serviceList.map(service => service.id) providerStats['intervals'].forEach(function (interval) { let intervalTime = (parseInt(interval.time[1]) - parseInt(interval.time[0])) / 60 providerAvailableTime += intervalTime let intervalServices = interval.services.length === 0 ? providerServices : interval.services intervalServices.forEach(function (serviceId) { if (!(serviceId in serviceData)) { serviceData[serviceId] = { count: 0, available: 0, occupied: 0, revenue: 0 } } serviceData[serviceId].available += intervalTime }) }) employeeData[providerId].available += providerAvailableTime employeeData[providerId].occupied += parseInt(providerStats.occupied) employeeData[providerId].count += parseInt(providerStats.count) employeeData[providerId].revenue += parseFloat(providerStats.revenue) } for (let packageId in data[dateKey].packages) { if (!data[dateKey].packages.hasOwnProperty(packageId)) { continue } let packagesStats = data[dateKey].packages[packageId] packageData[packageId].occupied += parseInt(packagesStats.occupied) packageData[packageId].count += parseInt(packagesStats.count) packageData[packageId].revenue += parseFloat(packagesStats.revenue) packageData[packageId].purchased += parseInt(packagesStats.purchased) } } let employeeTableData = [] for (let providerId in employeeData) { if (!employeeData.hasOwnProperty(providerId)) { continue } let provider = this.getProviderById(parseInt(providerId)) employeeTableData.push({ provider, employeeName: provider.firstName + ' ' + provider.lastName, employeePhoto: provider.pictureThumbPath, numAppointments: employeeData[providerId].count, sumPayments: employeeData[providerId].revenue, hoursAppointment: employeeData[providerId].occupied, load: employeeData[providerId].available !== 0 ? parseFloat((employeeData[providerId].occupied / employeeData[providerId].available * 100).toFixed(1)) : 0, available: employeeData[providerId].available, occupied: employeeData[providerId].occupied }) } this.employeeTableParams.total = employeeTableData.length employeeTableData = employeeTableData.sort((a, b) => (a.employeeName > b.employeeName) ? 1 : -1) this.employeeTableData = employeeTableData this.showVisibleEmployeeTableData() let serviceTableData = [] for (let serviceId in serviceData) { if (!serviceData.hasOwnProperty(serviceId)) { continue } let service = this.getServiceById(parseInt(serviceId)) serviceTableData.push({ service, serviceName: service.name, servicePhoto: service.pictureThumbPath, numAppointments: serviceData[serviceId].count, sumPayments: serviceData[serviceId].revenue, hoursAppointment: serviceData[serviceId].occupied, load: serviceData[serviceId].available !== 0 ? parseFloat((serviceData[serviceId].occupied / serviceData[serviceId].available * 100).toFixed(1)) : 0 }) } this.serviceTableParams.total = serviceTableData.length serviceTableData = serviceTableData.sort((a, b) => (a.serviceName > b.serviceName) ? 1 : -1) this.serviceTableData = serviceTableData this.showVisibleServiceTableData() let packageTableData = [] for (let packageId in packageData) { if (!packageData.hasOwnProperty(packageId)) { continue } let pack = this.getPackageById(parseInt(packageId)) packageTableData.push({ pack, packageName: pack.name, packagePhoto: pack.pictureThumbPath, numPurchased: packageData[packageId].purchased, numAppointments: packageData[packageId].count, sumPayments: packageData[packageId].revenue, hoursAppointment: packageData[packageId].occupied }) } this.packageTableParams.total = packageTableData.length packageTableData = packageTableData.sort((a, b) => (a.packageName > b.packageName) ? 1 : -1) this.packageTableData = packageTableData this.showVisiblePackageTableData() }, fillCustomersChart (data) { this.customersChartData.datasets[0].data.splice(0, 1, data.newCustomersCount) this.customersChartData.datasets[0].data.splice(1, 1, data.returningCustomersCount) this.customersChartData.datasets[0].data.splice(2, 1, (this.newCustomers === 0 && this.returningCustomers === 0) ? 1 : 0) this.totalPastPeriodCustomers = data.totalPastPeriodCustomers }, fillEmployeesChart (data) { this.employeesChartData.labels = [] this.employeesChartData.datasets[0].data = [] this.employeesChartData.datasets[1].data = [] for (let i = 0; i < data.length; i++) { this.employeesChartData.labels.push(data[i].name) this.employeesChartData.datasets[0].data.push(data[i].views) this.employeesChartData.datasets[1].data.push(data[i].appointments) } }, filterEmployeesChart () { let employeesStats = [] let employeesToRemoveFromStats = [] for (let i = 0; i < this.employeesStats.length; i++) { if (_.indexOf(this.employees, this.employeesStats[i].id) === -1) { employeesToRemoveFromStats.push(this.employeesStats[i]) } } if (_.difference(this.employeesStats, employeesToRemoveFromStats).length === 0) { employeesStats = this.employees.length === 0 ? this.employeesStats : [] } else { employeesStats = _.difference(this.employeesStats, employeesToRemoveFromStats) } this.fillEmployeesChart(employeesStats) if (typeof this.$refs.employeesChart !== 'undefined') { this.$refs.employeesChart.update() } }, fillServicesChart (data) { this.servicesChartData.labels = [] this.servicesChartData.datasets[0].data = [] this.servicesChartData.datasets[1].data = [] for (let i = 0; i < data.length; i++) { this.servicesChartData.labels.push(data[i].name) this.servicesChartData.datasets[0].data.push(data[i].views) this.servicesChartData.datasets[1].data.push(data[i].appointments) } }, filterServicesChart () { let servicesStats = [] let servicesToRemoveFromStats = [] for (let i = 0; i < this.servicesStats.length; i++) { if (_.indexOf(this.services, this.servicesStats[i].id) === -1) { servicesToRemoveFromStats.push(this.servicesStats[i]) } } if (_.difference(this.servicesStats, servicesToRemoveFromStats).length === 0) { servicesStats = this.services.length === 0 ? this.servicesStats : [] } else { servicesStats = _.difference(this.servicesStats, servicesToRemoveFromStats) } this.fillServicesChart(servicesStats) if (typeof this.$refs.servicesChart !== 'undefined') { this.$refs.servicesChart.update() } }, fillLocationsChart (data) { this.locationsChartData.labels = [] this.locationsChartData.datasets[0].data = [] this.locationsChartData.datasets[1].data = [] for (let i = 0; i < data.length; i++) { this.locationsChartData.labels.push(data[i].name) this.locationsChartData.datasets[0].data.push(data[i].views) this.locationsChartData.datasets[1].data.push(data[i].appointments) } }, filterLocationsChart () { let locationsStats = [] let locationsToRemoveFromStats = [] for (let i = 0; i < this.locationsStats.length; i++) { if (_.indexOf(this.locations, this.locationsStats[i].id) === -1) { locationsToRemoveFromStats.push(this.locationsStats[i]) } } if (_.difference(this.locationsStats, locationsToRemoveFromStats).length === 0) { locationsStats = this.locations.length === 0 ? this.locationsStats : [] } else { locationsStats = _.difference(this.locationsStats, locationsToRemoveFromStats) } this.fillLocationsChart(locationsStats) if (typeof this.$refs.locationsChart !== 'undefined') { this.$refs.locationsChart.update() } }, calculateChartTotal (name) { switch (name) { case ('count'): let appointmentsCount = 0 this.smallBarChartAppointmentsData.datasets[0].data.forEach(function (value) { appointmentsCount += (value !== null ? value : 0) }) return appointmentsCount case ('load'): let availableSum = 0 let occupiedSum = 0 for (let key in this.employeeTableData) { availableSum += this.employeeTableData[key].available occupiedSum += this.employeeTableData[key].occupied } return (availableSum !== 0 ? parseFloat((occupiedSum / availableSum * 100).toFixed(1)) : 0) + '%' case ('revenue'): let appointmentsRevenue = 0 this.smallLineChartRevenueData.datasets[0].data.forEach(function (value) { appointmentsRevenue += (value !== null ? value : 0) }) return this.getFormattedPrice(appointmentsRevenue) } }, getCurrentUser () { this.$http.get(`${this.$root.getAjaxUrl}/users/current`) .then(response => { this.currentUser = response.data.data.user }) .catch(e => { console.log('getCurrentUser fail') }) }, getPercentageBarColor (percent) { switch (true) { case (percent < 25): return '#FF1563' case (percent > 25 && percent < 50): return '#FFA700' case (percent > 50 && percent < 75): return '#BDDE00' case (percent > 75): return '#5FCE19' default: return '#5FCE19' } }, growthClass (value) { if (value > 0 || value === '+∞') { return 'am-growth-increase' } if (value < 0 || value === '-∞') { return 'am-growth-decrease' } return 'am-growth-equal' }, growthPercentageCharacter (value) { if (value === '+∞' || value === '-∞') { return '' } return '%' }, growthPercentage (totalValue, pastTotalValue) { if (totalValue === 0 && pastTotalValue === 0) { return 0 } if (totalValue === 0 && pastTotalValue !== 0) { return '-∞' } if (totalValue !== 0 && pastTotalValue === 0) { return '+∞' } return totalValue - pastTotalValue === 0 ? 0 : ((totalValue - pastTotalValue) / pastTotalValue * 100).toFixed(1) }, openRecurringAppointment (id) { this.dialogAppointment = false setTimeout(() => { this.showDialogEditAppointment(id) }, 200) } }, computed: { newCustomers () { return this.customersChartData.datasets[0].data[0] }, returningCustomers () { return this.customersChartData.datasets[0].data[1] }, totalCustomers () { return this.newCustomers + this.returningCustomers }, newCustomersPercentage () { return this.totalCustomers === 0 ? 0 : parseFloat((this.newCustomers / this.totalCustomers * 100).toFixed(1)) }, returnedCustomersPercentage () { return this.totalCustomers === 0 ? 0 : parseFloat((this.returningCustomers / this.totalCustomers * 100).toFixed(1)) }, countGrowthPercentage () { return this.growthPercentage(this.selectedPeriodStats.count, this.previousPeriodStats.count) }, countGrowthClass () { return this.growthClass(this.countGrowthPercentage) }, revenueGrowthPercentage () { return this.growthPercentage(this.selectedPeriodStats.revenue, this.previousPeriodStats.revenue) }, revenueGrowthClass () { return this.growthClass(this.revenueGrowthPercentage) }, revenueGrowthPercentageCharacter () { return this.growthPercentageCharacter(this.revenueGrowthPercentage) }, loadGrowthPercentage () { return this.growthPercentage(this.selectedPeriodStats.occupied, this.previousPeriodStats.occupied) }, loadGrowthClass () { return this.growthClass(this.loadGrowthPercentage) }, loadGrowthPercentageCharacter () { return this.growthPercentageCharacter(this.loadGrowthPercentage) }, customersGrowthPercentage () { return this.growthPercentage(this.totalCustomers, this.totalPastPeriodCustomers) }, customerGrowthClass () { return this.growthClass(this.customersGrowthPercentage) }, customerGrowthPercentageCharacter () { return this.growthPercentageCharacter(this.customersGrowthPercentage) } }, components: { GetPremiumBanner, BarChart, DoughnutChart, LineChart, DialogCustomer, DialogAppointment, DialogPayment, PageHeader, DialogExport, AppointmentListCollapsed, PaginationBlock, DialogNewCustomize } } </script> <style lang="less"> .am-amelia-banner { position: relative; display: flex; align-items: center; justify-content: space-between; min-height: 272px; box-sizing: border-box; margin-top: 24px; background: #ffffff; border-radius: 4px; @media only screen and (max-width: 992px) { flex-wrap: wrap; } * { color: #FFFFFF; } &__content { display: flex; flex-direction: column; width: 100%; &-main { display: flex; justify-content: space-between; &-left { padding: 40px; p { font-size: 14px; line-height: 20px; color: #1A2C37; } &-logo { margin-bottom: 16px; img { height: 36px; width: auto; } } &-subtitle { p { font-weight: 600; margin: 4px 0; } } &-text { p { max-width: 600px; font-weight: 400; margin: 4px 0; } } &-rating { display: flex; flex-direction: row; align-items: center; margin-bottom: 16px; &-text { margin: 4px; font-weight: 400; } } &-buttons { display: flex; .am-promo-btn.el-button.el-button--primary { display: flex; width: fit-content; background: #1246D6; border-color: #1246D6; border-radius: 8px; padding: 8px 20px; span { display: flex; align-items: center; line-height: 20px; font-size: 14px; } img { margin-left: 8px; } } } @media only screen and (max-width: 1360px) { width: 30%; } @media only screen and (max-width: 1180px) { width: 65%; z-index: 99; } @media only screen and (max-width: 620px) { width: 100%; } @media only screen and (max-width: 380px) { padding: 20px; } } &-right { img { border-radius: 0 4px 4px 0; height: 100%; width: 100%; } @media only screen and (max-width: 1180px) { position: absolute; right: 0; height: 100%; width: 55%; } @media only screen and (max-width: 620px) { display: none; } } } } &__buttons { width: 0; @media only screen and (max-width: 992px) { max-width: 100%; display: flex; justify-content: center; } p { position: absolute; bottom: 8px; right: 12px; margin: 0; cursor: pointer; } a { display: inline-block; font-size: 16px; font-weight: 500; line-height: 1.5; text-decoration: none; background-color: #005AEE; color: #ffffff; padding: 8px 20px; border-radius: 9px; margin-left: 16px; &:hover { background-color: #0041af; } &:active, &:hover, &:focus { text-decoration: none; color: #ffffff; } @media only screen and (max-width: 992px) { margin: 16px 0 0 0; } } } } </style>
Save
Back