PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
2.4.8 2.4.7 2.4.6 2.4.5 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 / _fields / ServiceFormField.vue
ameliabooking / v3 / src / views / public / StepForm / _fields Last commit date
EmployeeFormField.vue 1 month ago LocationFormField.vue 1 month ago ServiceFormField.vue 1 day ago
ServiceFormField.vue
387 lines
1 <template>
2 <el-form-item
3 v-if="
4 store.state.entities.services.length > 1 &&
5 store.state.entities.preselected.service.length !== 1
6 "
7 class="am-fs__init-form__item"
8 :prop="store.state.entities.categories.length > 1 ? 'service' : 'onlyService'"
9 :class="'am-service-select'"
10 label-position="top"
11 >
12 <template #label>
13 <span class="am-fs__init-form__label"> {{ amLabels.service_colon }}: </span>
14 </template>
15 <AmAdvancedSelect
16 v-if="store.state.entities.categories.length > 1"
17 v-model="initFormData.service"
18 :filterable="props.filterable"
19 :options="categoryOptions"
20 :props-data="advSelectProps"
21 :placeholder="amLabels.select_service"
22 :category-name="amLabels.dropdown_category_heading"
23 :sub-category-name="amLabels.dropdown_items_heading"
24 :empty-state-string="amLabels.dropdown_empty"
25 :tax-options="taxServicesOptions"
26 :tax-label="`+${amLabels.total_tax_colon}`"
27 :tax-label-incl="amLabels.incl_tax"
28 :tax-visible="props.taxVisible"
29 :aria-label="amLabels.select_service"
30 :multiple-prices="multipleServicePricesOptions"
31 :multiple-prices-label="amLabels.from"
32 @change="changeService"
33 ></AmAdvancedSelect>
34 <AmSelect
35 v-else
36 v-model="initFormData.onlyService"
37 clearable
38 :fit-input-width="true"
39 :filterable="props.filterable"
40 :popper-class="'am-service-dropdown'"
41 :placeholder="amLabels.select_service"
42 :aria-label="amLabels.select_service"
43 :no-match-text="amLabels.dropdown_empty"
44 :no-data-text="amLabels.dropdown_empty"
45 :filter-method="filterService"
46 @change="changeOnlyService"
47 >
48 <AmOption
49 v-for="service in filteredServices"
50 :key="service.id"
51 :value="service.id"
52 :label="service.name"
53 >
54 <div class="am-select-service">
55 <span class="am-select-service-name">
56 {{ service.name }}
57 </span>
58 <span
59 v-if="
60 service.price > 0 ||
61 (hasMultipleServicePrices(service.id) && props.multiplePricesVisibility)
62 "
63 class="am-select-service-price"
64 >
65 <template v-if="hasMultipleServicePrices(service.id) && props.multiplePricesVisibility">
66 {{ amLabels.from }} {{ useFormattedPrice(serviceLowestPrice[service.id]) }}
67 </template>
68 <template v-else-if="service.price">
69 {{ useFormattedPrice(service.price) }}
70 </template>
71 <span v-if="taxVisibility(service.id)" class="am-select-service-tax">
72 <template v-if="taxServicesOptions.find((a) => a.id === service.id).excluded">
73 {{ `+${amLabels.total_tax_colon}` }}
74 </template>
75 <template v-else>
76 {{ `${amLabels.incl_tax}` }}
77 </template>
78 </span>
79 </span>
80 </div>
81 </AmOption>
82 </AmSelect>
83 </el-form-item>
84 </template>
85
86 <script setup>
87 import { useStore } from 'vuex'
88 import { computed, inject, reactive, ref } from 'vue'
89 import AmSelect from '../../../_components/select/AmSelect'
90 import AmOption from '../../../_components/select/AmOption'
91 import AmAdvancedSelect from '../../../_components/advanced-select/AmAdvancedSelect'
92 import useAction from '../../../../assets/js/public/actions'
93 import { useFormattedPrice } from '../../../../assets/js/common/formatting'
94 import { useEntityTax } from '../../../../assets/js/common/pricing'
95 import { getServicePrices } from '../../../../assets/js/public/catalog'
96
97 let props = defineProps({
98 filterable: {
99 type: Boolean,
100 default: true,
101 },
102 taxVisible: {
103 type: Boolean,
104 default: true,
105 },
106 multiplePricesVisibility: {
107 type: Boolean,
108 default: false,
109 },
110 })
111
112 // * Amelia Settings
113 const amSettings = inject('settings')
114
115 // * Store
116 let store = useStore()
117
118 // * Labels
119 const amLabels = inject('amLabels')
120
121 // * Sidebar steps
122 let { sidebarDataCollector } = inject('sidebarStepsFunctions', {
123 sidebarDataCollector: () => {},
124 })
125
126 const { changeInitStepDataService } = inject('initDataChanges', {
127 changeInitStepDataService: () => {},
128 })
129
130 const { footerBtnDisabledUpdater } = inject('changingStepsFunctions', {
131 footerBtnDisabledUpdater: () => {},
132 })
133
134 // Container Width
135 let cWidth = inject('containerWidth', 0)
136 let checkScreen = computed(() => cWidth.value < 560 || cWidth.value - 240 < 520)
137
138 /**
139 * * Category - Service Block
140 */
141
142 let initFormData = inject('initFormData')
143
144 // * Service select props
145 let advSelectProps = reactive({
146 expandTrigger: checkScreen.value ? 'click' : 'hover',
147 multiple: false,
148 checkStrictly: false,
149 emitPath: true,
150 lazy: false,
151 value: 'id',
152 label: 'name',
153 children: 'serviceList',
154 disabled: 'disabled',
155 leaf: 'leaf',
156 })
157
158 let serviceLowestPrice = ref({})
159
160 const entitiesRelations = computed(() => store.getters['entities/getEntitiesRelations'])
161
162 // * Set Category Options from entities
163 let categoryOptions = computed(() =>
164 store.getters['entities/filteredCategories'](
165 Object.assign(store.getters['booking/getSelection'], { serviceId: null, categoryId: null }),
166 ),
167 )
168
169 // * Set Service Options from entities
170 let serviceOptions = computed(() =>
171 store.getters['entities/filteredServices'](
172 Object.assign(store.getters['booking/getSelection'], { serviceId: null, categoryId: null }),
173 ),
174 )
175 let taxServicesOptions = computed(() => {
176 let arr = []
177
178 if (amSettings.payments.taxes.enabled) {
179 serviceOptions.value.forEach((service) => {
180 let tax = useEntityTax(store, service.id, 'service')
181 if (tax) {
182 let obj = {
183 id: service.id,
184 excluded: amSettings.payments.taxes.excluded,
185 }
186 arr.push(obj)
187 }
188 })
189 }
190
191 return arr
192 })
193
194 let multipleServicePricesOptions = computed(() => {
195 if (!props.multiplePricesVisibility) {
196 return {}
197 }
198
199 let obj = {}
200 serviceOptions.value.forEach((service) => {
201 obj[service.id] = {
202 visible: hasMultipleServicePrices(service.id),
203 price: serviceLowestPrice.value[service.id],
204 }
205 })
206 return obj
207 })
208
209 // * Filter service
210 let queryLower = ref('')
211
212 function filterService(query) {
213 queryLower.value = query.toLowerCase()
214 }
215
216 let filteredServices = computed(() => {
217 if (queryLower.value) {
218 return serviceOptions.value.filter((item) => {
219 return item.name.toLowerCase().includes(queryLower.value)
220 })
221 }
222
223 return serviceOptions.value
224 })
225
226 /**
227 * Change Service function
228 * @param val
229 */
230 let changeService = (val) => {
231 let serviceData = {
232 reference: 'service',
233 // position will depends on fields order
234 position: 0,
235 value: '',
236 }
237
238 if (Array.isArray(val)) {
239 serviceData.value = categoryOptions.value
240 .find((item) => item.id === val[0])
241 .serviceList.find((item) => item.id === val[1]).name
242 }
243
244 if (!store.getters['booking/getPackageId']) {
245 changeInitStepDataService()
246 }
247
248 useAction(store, {}, 'SelectService', 'appointment', null, null)
249
250 sidebarDataCollector(serviceData)
251
252 footerBtnDisabledUpdater(false)
253 }
254
255 function changeOnlyService(val) {
256 let serviceData = {
257 reference: 'service',
258 // position will depends on fields order
259 position: 0,
260 value: '',
261 }
262
263 if (val) {
264 let service = serviceOptions.value.find((item) => item.id === val)
265 serviceData.value = service ? service.name : ''
266 }
267
268 changeInitStepDataService()
269
270 useAction(store, {}, 'SelectService', 'appointment', null, null)
271
272 sidebarDataCollector(serviceData)
273 }
274
275 function taxVisibility(id) {
276 return props.taxVisible && !!taxServicesOptions.value.filter((a) => a.id === id).length
277 }
278
279 function hasMultipleServicePrices(id) {
280 let arrPrice = []
281
282 let service = serviceOptions.value.find((service) => service.id === id)
283 // Service pricing
284 if (service) {
285 arrPrice.push(...getServicePrices(service))
286 }
287
288 let employees = store.getters['entities/filteredEmployees'](store.getters['booking/getSelection'])
289
290 let selectedEmployeeId = store.getters['booking/getSelection'].providerId
291
292 if (selectedEmployeeId) {
293 let employee = employees.find((employee) => employee.id === selectedEmployeeId)
294 if (
295 employee &&
296 selectedEmployeeId in entitiesRelations.value &&
297 id in entitiesRelations.value[selectedEmployeeId]
298 ) {
299 let employeeService = employee.serviceList.find((service) => service.id === id)
300
301 // Employee pricing
302 if (employeeService) {
303 arrPrice.push(...getServicePrices(employeeService))
304 }
305 }
306 } else {
307 employees.forEach((employee) => {
308 if (employee.id in entitiesRelations.value && id in entitiesRelations.value[employee.id]) {
309 let employeeService = employee.serviceList.find((service) => service.id === id)
310
311 // Employee pricing
312 if (employeeService) {
313 arrPrice.push(...getServicePrices(employeeService))
314 }
315 }
316 })
317 }
318
319 if (!arrPrice.length) {
320 return false
321 }
322
323 let serviceMinPrice = arrPrice.reduce((prev, curr) => {
324 return curr < prev ? curr : prev
325 }, arrPrice[0])
326
327 let serviceMaxPrice = arrPrice.reduce((prev, curr) => {
328 return curr > prev ? curr : prev
329 }, arrPrice[0])
330
331 serviceLowestPrice.value[id] = serviceMinPrice
332
333 return serviceMinPrice !== serviceMaxPrice
334 }
335 </script>
336
337 <script>
338 export default {
339 name: 'ServiceFormField',
340 }
341 </script>
342
343 <style lang="scss">
344 .am-service-dropdown {
345 .am-select-service {
346 --am-c-ss-item-name: var(--am-c-option-text);
347 --am-c-ss-item-price: var(--am-c-option-selected);
348
349 width: 100%;
350 display: flex;
351 flex-wrap: wrap;
352 align-items: center;
353 justify-content: space-between;
354
355 &-name {
356 display: block;
357 font-size: 14px;
358 font-weight: 400;
359 line-height: 1.43;
360 color: var(--am-c-ss-item-name);
361 white-space: nowrap;
362 overflow: hidden;
363 text-overflow: ellipsis;
364 width: 100%;
365 }
366
367 &-price {
368 font-size: 14px;
369 font-weight: 400;
370 line-height: 1.714;
371 color: var(--am-c-ss-item-price);
372 }
373
374 &-tax {
375 display: inline-flex;
376 font-size: 14px;
377 font-weight: 400;
378 line-height: 1;
379 color: var(--am-c-ss-item-price);
380 background-color: var(--am-c-option-selected-op10);
381 border-radius: 10px;
382 padding: 3px 8px;
383 }
384 }
385 }
386 </style>
387