PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / public / StepForm / EmployeeStep / EmployeeStep.vue
ameliabooking / v3 / src / views / public / StepForm / EmployeeStep Last commit date
EmployeeStep.vue 5 days ago
EmployeeStep.vue
389 lines
1 <template>
2 <StepCardLayout
3 ref="stepCardLayoutReference"
4 :custom-class="[props.globalClass, 'am-fs-employee-step']"
5 :card-selected="store.getters['booking/getEmployeeId'] !== null || anyEmployee"
6 :allow-popup="
7 serviceStepsIndex < 0 && stepIndex === 0 && store.getters['booking/getServiceId'] !== null
8 "
9 >
10 <!-- Any Employee -->
11 <div
12 v-if="customizeOptions.employee.required === false"
13 class="am-fs__init-item"
14 :class="{
15 'am--selected': anyEmployee,
16 }"
17 :tabindex="keyboardSelectionDisabled ? -1 : 0"
18 @click="chooseAnyEmployee"
19 >
20 <div class="am-fs__init-item__img am-item-any">
21 <span class="am-icon-user" />
22 </div>
23 <div class="am-fs__init-item__content am-item-any">
24 <div class="am-fs__init-item__heading" :class="responsiveClass">
25 <div class="am-fs__init-item__name" :class="responsiveClass">
26 {{ amLabels.any_employee }}
27 </div>
28 </div>
29 </div>
30 </div>
31 <!-- /Any Employee -->
32
33 <StepCard
34 v-for="employee in employeeOptions"
35 :key="employee.id"
36 :item="employee"
37 :item-name="`${employee.firstName} ${employee.lastName}`"
38 :selected="store.getters['booking/getEmployeeId'] === employee.id"
39 :disabled="disabledEmployee(employee)"
40 :is-person="true"
41 :price="getEmployeePrice(employee)"
42 :price-visibility="customizeOptions.price.visibility && getEmployeePrice(employee) !== ''"
43 :labels="amLabels"
44 :parent-width="componentWidth"
45 :keyboard-selection-disabled="keyboardSelectionDisabled"
46 :info-btn-visibility="!!employee.description && customizeOptions.moreBtn.visibility"
47 @select-item="selectEmployee(employee)"
48 @trigger-info-popup="
49 () => {
50 moreInfoVisibility = true
51 selectedEmployee = employee
52 }
53 "
54 />
55 </StepCardLayout>
56
57 <!-- More Info -->
58 <MoreInfoPopup
59 v-if="moreInfoVisibility"
60 v-model:visibility="moreInfoVisibility"
61 :heading="amLabels.employee_information"
62 :labels="amLabels"
63 :item="selectedEmployee"
64 :item-name="`${selectedEmployee.firstName} ${selectedEmployee.lastName}`"
65 :is-person="true"
66 />
67 <!-- /More Info -->
68 </template>
69
70 <script setup>
71 // * Import from Vue
72 import { computed, inject, ref, onMounted, reactive, onUnmounted, watch } from 'vue'
73
74 // * Import components
75 import StepCardLayout from '../common/StepCardLayout.vue'
76 import StepCard from '../common/StepCard.vue'
77 import MoreInfoPopup from '../common/MoreInfoPopup.vue'
78
79 // * Import from Vuex
80 import { useStore } from 'vuex'
81
82 // * Composables
83 import { useFormattedPrice } from '../../../../assets/js/common/formatting'
84 import { useCartItem } from '../../../../assets/js/public/cart'
85 import useAction from '../../../../assets/js/public/actions'
86 import { useResponsiveClass } from '../../../../assets/js/common/responsive'
87 import { useElementSize } from '@vueuse/core'
88 import { defaultCustomizeSettings } from '../../../../assets/js/common/defaultCustomize'
89
90 // * Store
91 const store = useStore()
92
93 // * Props
94 const props = defineProps({
95 globalClass: {
96 type: String,
97 default: '',
98 },
99 })
100
101 // * Amelia Settings
102 const amSettings = inject('settings')
103
104 // * Entities
105 let amEntities = computed(() => {
106 return store.state.entities
107 })
108
109 let locationsRules = computed(() => store.getters['entities/getLocations'].length <= 1)
110
111 // * Customize
112 const amCustomize = inject('amCustomize')
113
114 // * Component reference
115 const stepCardLayoutReference = ref(null)
116 const stepCardLayoutDom = computed(() => stepCardLayoutReference.value?.stepCardLayoutRef || null)
117
118 // * Component width
119 const { width: componentWidth } = useElementSize(stepCardLayoutDom)
120
121 let responsiveClass = computed(() => {
122 return useResponsiveClass(componentWidth.value)
123 })
124
125 // * Steps Array
126 const stepsArray = inject('stepsArray', ref([]))
127 const serviceStepsIndex = computed(() => {
128 return stepsArray.value.findIndex((step) => step.name === 'ServiceStep')
129 })
130
131 // * Step Index
132 const stepIndex = inject('stepIndex', 0)
133
134 // * Labels
135 let labels = inject('labels')
136
137 // * local language short code
138 const localLanguage = inject('localLanguage')
139
140 // * if local lang is in settings lang
141 let langDetection = computed(() => amSettings.general.usedLanguages.includes(localLanguage.value))
142
143 // * Labels
144 const amLabels = computed(() => {
145 let computedLabels = reactive({ ...labels })
146
147 const customizedLabels = amCustomize.employeeStep?.translations
148
149 if (customizedLabels) {
150 Object.keys(customizedLabels).forEach((labelKey) => {
151 const labelData = customizedLabels[labelKey]
152 const localizedLabel = labelData[localLanguage.value]
153
154 if (localizedLabel && langDetection.value) {
155 computedLabels[labelKey] = localizedLabel
156 } else if (labelData.default) {
157 computedLabels[labelKey] = labelData.default
158 }
159 })
160 }
161
162 return computedLabels
163 })
164
165 // * Sidebar steps
166 let { sidebarDataCollector } = inject('sidebarStepsFunctions', {
167 sidebarDataCollector: () => {},
168 })
169
170 const { footerBtnDisabledUpdater } = inject('changingStepsFunctions', {
171 footerBtnDisabledUpdater: () => {},
172 })
173
174 let customizeOptions = computed(
175 () => amCustomize.employeeStep?.options || defaultCustomizeSettings.sbsNew.employeeStep.options,
176 )
177
178 // * Any Employee
179 let anyEmployee = computed(() => !store.getters['booking/getEmployeeId'])
180
181 // * Set Employee Options from entities
182 let initStepOrder = [{ id: 'ServiceStep' }, { id: 'EmployeeStep' }, { id: 'LocationStep' }]
183
184 let stepOrder = computed(() => {
185 return 'order' in amCustomize ? amCustomize.order : initStepOrder
186 })
187
188 const filterRules = computed(() => {
189 const idx = stepOrder.value.findIndex((step) => step.id === 'EmployeeStep')
190 let arrRules = stepOrder.value.slice(0, idx)
191 const obj = { serviceId: null, providerId: null, locationId: null }
192 if (arrRules.some((step) => step.id === 'ServiceStep')) delete obj.serviceId
193 if (arrRules.some((step) => step.id === 'LocationStep') || locationsRules.value)
194 delete obj.locationId
195 return obj
196 })
197
198 let employeeOptions = computed(() =>
199 store.getters['entities/filteredEmployees'](
200 Object.assign(store.getters['booking/getSelection'], filterRules.value),
201 ),
202 )
203
204 function getEmployeePrice(employee) {
205 if (store.getters['booking/getSelection'].serviceId) {
206 let employeeServices = store.getters['entities/getEmployeeServices']({
207 serviceId: store.getters['booking/getSelection'].serviceId,
208 providerId: employee.id,
209 })
210 let service = store.getters['entities/getBookableFromBookableEntities'](
211 store.getters['booking/getSelection'],
212 )
213
214 if (employeeServices.length === 0 || !service) {
215 return ''
216 }
217 let priceDiff = employeeServices[0].price - service.price
218 let sign = priceDiff > 0 ? (service.price > 0 ? '+' : '') : '-'
219 return priceDiff !== 0 ? sign + useFormattedPrice(priceDiff) : ''
220 }
221
222 return ''
223 }
224
225 function selectEmployee(employee) {
226 if (disabledEmployee(employee)) {
227 return
228 }
229
230 // Sidebar employee data
231 let employeeData = {
232 reference: 'employee',
233 // position will depends on fields order
234 position: 0,
235 value:
236 employee.id !== store.getters['booking/getEmployeeId']
237 ? `${employee.firstName} ${employee.lastName}`
238 : '',
239 }
240
241 if (employee.id !== store.getters['booking/getEmployeeId']) {
242 store.commit('booking/setEmployeeId', employee.id)
243 } else {
244 store.commit('booking/setEmployeeId', null)
245 }
246
247 writeEmployeeData(employeeData)
248 }
249 function disabledEmployee(employee) {
250 const relations = amEntities.value.entitiesRelations
251 const serviceId = store.getters['booking/getServiceId']
252 const locationId = store.getters['booking/getLocationId']
253
254 if (locationId) {
255 if (serviceId) {
256 return !(
257 relations[employee.id] &&
258 serviceId in relations[employee.id] &&
259 relations[employee.id][serviceId].includes(locationId)
260 )
261 }
262
263 return Object.values(relations[employee.id]).every((sIds) => !sIds.includes(locationId))
264 }
265
266 return false
267 }
268
269 function chooseAnyEmployee() {
270 // Sidebar employee data
271 let employeeData = {
272 reference: 'employee',
273 // position will depends on fields order
274 position: 0,
275 value: '',
276 }
277
278 store.commit('booking/setEmployeeId', null)
279
280 writeEmployeeData(employeeData)
281 }
282
283 function writeEmployeeData(employeeData) {
284 let cartItem = useCartItem(store)
285
286 store.commit('booking/unsetMultipleAppointmentsData', cartItem.index)
287
288 sidebarDataCollector(employeeData)
289
290 useAction(store, {}, 'SelectEmployee', 'appointment', null, null)
291
292 if (customizeOptions.value.employee.required) {
293 if (employeeData.value) {
294 footerBtnDisabledUpdater(false)
295 } else {
296 footerBtnDisabledUpdater(true)
297 }
298 } else {
299 footerBtnDisabledUpdater(false)
300 }
301 }
302
303 // * Employee - More Info
304 let moreInfoVisibility = ref(false)
305 let selectedEmployee = ref(null)
306
307 const keyboardSelectionDisabled = computed(() => moreInfoVisibility.value)
308
309 watch(moreInfoVisibility, (isVisible, wasVisible) => {
310 if (wasVisible && !isVisible) {
311 stepCardLayoutReference.value?.focusSelectedOrFirstItem?.()
312 }
313 })
314
315 // * Mounted hook
316 onMounted(() => {
317 if (
318 !!customizeOptions.value.employee.required &&
319 store.getters['booking/getEmployeeId'] === null
320 ) {
321 footerBtnDisabledUpdater(true)
322 }
323 })
324
325 onUnmounted(() => {
326 footerBtnDisabledUpdater(false)
327 })
328 </script>
329
330 <script>
331 export default {
332 name: 'EmployeeStep',
333 key: 'employeeStep',
334 sidebarData: {
335 label: 'employee_selection',
336 icon: 'employee',
337 stepSelectedData: [],
338 finished: false,
339 selected: false,
340 },
341 }
342 </script>
343
344 <style lang="scss">
345 .amelia-v2-booking #amelia-container {
346 .am-fs-employee-step {
347 .am-fs__init {
348 &-item {
349 // Card content
350 &__content {
351 &.am-item-any {
352 justify-content: center;
353 }
354 }
355
356 // Card heading
357 &__heading {
358 &.am-rw-370 {
359 flex-wrap: nowrap;
360 justify-content: unset;
361 }
362 }
363
364 // Card name
365 &__name {
366 &.am-rw-370 {
367 display: block;
368 white-space: nowrap;
369 overflow: hidden;
370 text-overflow: ellipsis;
371 width: auto;
372 order: unset;
373 justify-self: flex-start;
374 }
375 }
376
377 // Card cost
378 &__cost {
379 &.am-rw-370 {
380 flex: 0 0 auto;
381 margin-left: auto;
382 }
383 }
384 }
385 }
386 }
387 }
388 </style>
389