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 / admin / customize / steps / StepForm / ServiceStep.vue
ameliabooking / v3 / src / views / admin / customize / steps / StepForm Last commit date
common 3 days ago BringingAnyone.vue 3 days ago CartStep.vue 3 days ago Congratulations.vue 3 days ago DateTimeStep.vue 3 days ago EmployeeStep.vue 3 days ago Extras.vue 3 days ago InfoStep.vue 3 days ago InitStep.vue 3 days ago LocationStep.vue 3 days ago PackageAppointmentsListStep.vue 3 days ago PackageAppointmentsStep.vue 3 days ago PackageInfoStep.vue 3 days ago PackageStep.vue 3 days ago PaymentStep.vue 3 days ago RecurringStep.vue 3 days ago RecurringSummary.vue 3 days ago ServiceStep.vue 3 days ago
ServiceStep.vue
454 lines
1 <template>
2 <StepCardLayout
3 ref="stepCardLayoutReference"
4 :custom-class="[props.class, 'am-fs-service-step']"
5 :allow-popup="true"
6 :card-selected="selectedService !== null"
7 >
8 <template #filters>
9 <AmInput
10 v-if="serviceCustomizeOptions.search.visibility"
11 v-model="searchFilter"
12 :placeholder="labelsDisplay('filter_input')"
13 :prefix-icon="iconSearch"
14 />
15 <AmSelect
16 v-if="serviceCustomizeOptions.category.visibility"
17 v-model="categoryFilter"
18 :placeholder="labelsDisplay('select_service_category')"
19 class="am-fs__init-category-select"
20 :filterable="serviceCustomizeOptions.category.filterable"
21 clearable
22 :fit-input-width="true"
23 :no-match-text="labelsDisplay('dropdown_empty')"
24 >
25 <AmOption
26 v-for="category in categoryOptions"
27 :key="category.id"
28 :value="category.id"
29 :label="category.name"
30 >
31 {{ category.name }}
32 </AmOption>
33 </AmSelect>
34 </template>
35
36 <!-- Card -->
37 <StepCard
38 v-for="service in serviceOptions"
39 :key="service.id"
40 :item="service"
41 :selected="selectedService === service.id"
42 :disabled="false"
43 :is-person="false"
44 :item-name="service.name"
45 :price="service.price"
46 :price-visibility="amCustomize[pageRenderKey].serviceStep.options.price.visibility"
47 :tax-visibility="
48 (licence.isPro || licence.isDeveloper) &&
49 amCustomize[pageRenderKey].serviceStep.options.tax.visibility
50 "
51 :tax-excluded="false"
52 :info-items="service.infoItems"
53 :info-btn-visibility="amCustomize[pageRenderKey].serviceStep.options.moreBtn.visibility"
54 :packages-btn-visibility="
55 features.packages && amCustomize[pageRenderKey].serviceStep.options.packagesBtn.visibility
56 "
57 :parent-width="componentWidth"
58 @select-item="selectService(service)"
59 @trigger-info-popup="
60 () => {
61 moreInfoVisibility = true
62 selectedService = service
63 }
64 "
65 />
66 <!-- /Card -->
67 </StepCardLayout>
68
69 <!-- More Info -->
70 <MoreInfoPopup
71 v-if="moreInfoVisibility"
72 v-model:visibility="moreInfoVisibility"
73 :heading="labelsDisplay('service_information')"
74 :item-name="selectedService.name"
75 :item="selectedService"
76 :is-person="false"
77 :employees-heading="!licence.isLite ? labelsDisplay('employees') : ''"
78 :employees-data="!licence.isLite ? employeesData : []"
79 :locations-heading="
80 licence.isBasic || licence.isPro || licence.isDeveloper ? labelsDisplay('locations') : ''
81 "
82 :locations-data="licence.isBasic || licence.isPro || licence.isDeveloper ? locationsData : []"
83 />
84 <!-- /More Info -->
85 </template>
86
87 <script setup>
88 // * Import for Vue
89 import { ref, inject, computed, watchEffect, provide } from 'vue'
90
91 // * Components
92 import AmSelect from '../../../../_components/select/AmSelect.vue'
93 import AmOption from '../../../../_components/select/AmOption.vue'
94 import AmInput from '../../../../_components/input/AmInput.vue'
95 import IconComponent from '../../../../_components/icons/IconComponent.vue'
96
97 // * Dedicated components
98 import StepCardLayout from './common/StepCardLayout.vue'
99 import StepCard from './common/StepCard.vue'
100 import MoreInfoPopup from '../../../../public/StepForm/common/MoreInfoPopup.vue'
101
102 // * Composables
103 import { useElementSize } from '@vueuse/core'
104
105 // * Props
106 const props = defineProps({
107 class: {
108 type: [String, Array],
109 default: '',
110 },
111 })
112
113 // * Step contains slide popups
114 let inPopup = ref(true)
115 provide('inPopup', { inPopup })
116
117 // * Features integrations
118 let features = inject('features')
119
120 // * Lang key
121 let langKey = inject('langKey')
122
123 // * Labels
124 let amLabels = inject('labels')
125
126 let stepName = inject('stepName')
127 let pageRenderKey = inject('pageRenderKey')
128 let amCustomize = inject('customize')
129
130 // * Plugin Licence
131 let licence = inject('licence')
132
133 // * Component reference
134 const stepCardLayoutReference = ref(null)
135 const stepCardLayoutDom = computed(() => stepCardLayoutReference.value?.stepCardLayoutRef || null)
136
137 // * Component width
138 const { width: componentWidth } = useElementSize(stepCardLayoutDom)
139
140 function labelsDisplay(label) {
141 return computed(() => {
142 return (
143 amCustomize.value[pageRenderKey.value]?.[stepName.value]?.translations?.[label]?.[
144 langKey.value
145 ] || amLabels[label]
146 )
147 }).value
148 }
149
150 // * FILTER
151 let searchFilter = ref('')
152 let categoryFilter = ref('')
153
154 // * Category options
155 let categoryOptions = ref([
156 { id: 1, name: 'Hair Services' },
157 { id: 2, name: 'Spa & Wellness' },
158 { id: 3, name: 'Beauty & Makeup' },
159 { id: 4, name: 'Medical Services' },
160 { id: 5, name: 'Fitness & Training' },
161 ])
162
163 let iconSearch = {
164 components: { IconComponent },
165 template: `<IconComponent icon="search"/>`,
166 }
167
168 let selectedService = ref(null)
169
170 let serviceOptions = ref([
171 {
172 id: 1,
173 name: 'Haircut & Styling',
174 price: '$45',
175 categoryId: 1,
176 infoItems: [
177 { icon: 'folder', name: 'Hair Services' },
178 { icon: 'clock', name: '60min' },
179 { icon: 'user', name: '1/15' },
180 { icon: 'locations', name: 'Nail Lounge' },
181 ],
182 pictureThumbPath: '',
183 },
184 {
185 id: 2,
186 name: 'Deep Tissue Massage',
187 price: '',
188 categoryId: 2,
189 infoItems: [
190 { icon: 'folder', name: 'Spa & Wellness' },
191 { icon: 'clock', name: '90min' },
192 { icon: 'user', name: '1/15 James Miller' },
193 { icon: 'locations', name: 'Medical Center' },
194 ],
195 },
196 {
197 id: 3,
198 name: 'Facial Treatment',
199 price: '$65',
200 categoryId: 2,
201 infoItems: [
202 { icon: 'folder', name: 'Spa & Wellness' },
203 { icon: 'clock', name: '75min' },
204 { icon: 'user', name: '1/15' },
205 { icon: 'locations', name: 'Fitness Gym' },
206 ],
207 },
208 {
209 id: 4,
210 name: 'Makeup Application',
211 price: '$55',
212 categoryId: 3,
213 infoItems: [
214 { icon: 'folder', name: 'Beauty & Makeup' },
215 { icon: 'clock', name: '45min' },
216 { icon: 'user', name: '1/15' },
217 { icon: 'locations', name: 'Downtown Salon' },
218 ],
219 },
220 {
221 id: 5,
222 name: 'Manicure & Pedicure',
223 price: '$35',
224 categoryId: 3,
225 infoItems: [
226 { icon: 'folder', name: 'Beauty & Makeup' },
227 { icon: 'clock', name: '60min' },
228 { icon: 'user', name: '1/15' },
229 { icon: 'locations', name: 'Wellness Center' },
230 ],
231 },
232 {
233 id: 6,
234 name: 'General Consultation',
235 price: '$120',
236 categoryId: 4,
237 infoItems: [
238 { icon: 'folder', name: 'Medical Services' },
239 { icon: 'clock', name: '30min' },
240 { icon: 'user', name: '1/15' },
241 { icon: 'locations', name: 'Beauty Spa' },
242 ],
243 },
244 {
245 id: 7,
246 name: 'Personal Training Session',
247 price: '$75',
248 categoryId: 5,
249 infoItems: [
250 { icon: 'folder', name: 'Fitness & Training' },
251 { icon: 'clock', name: '60min' },
252 { icon: 'user', name: '1/15' },
253 { icon: 'locations', name: 'Beauty Studio' },
254 ],
255 },
256 {
257 id: 8,
258 name: 'Hair Coloring',
259 price: '$95',
260 categoryId: 1,
261 infoItems: [
262 { icon: 'folder', name: 'Hair Services' },
263 { icon: 'clock', name: '120min' },
264 { icon: 'user', name: '1/15' },
265 { icon: 'locations', name: 'Color Studio' },
266 ],
267 },
268 ])
269
270 let serviceCustomizeOptions = computed(() => {
271 return amCustomize.value[pageRenderKey.value].serviceStep.options
272 })
273
274 const durationMap = {
275 1: '60min',
276 2: '90min',
277 3: '75min',
278 4: '45min',
279 5: '60min',
280 6: '30min',
281 7: '60min',
282 8: '120min',
283 }
284
285 const locationMap = {
286 1: 'Nail Lounge',
287 2: 'Medical Center',
288 3: 'Fitness Gym',
289 4: 'Downtown Salon',
290 5: 'Wellness Center',
291 6: 'Beauty Spa',
292 7: 'Beauty Studio',
293 8: 'Color Studio',
294 }
295 watchEffect(() => {
296 if (!serviceCustomizeOptions.value.serviceCategory.visibility) {
297 serviceOptions.value.forEach((service) => {
298 if (service.infoItems.some((item) => item.icon === 'folder')) {
299 service.infoItems = service.infoItems.filter((item) => item.icon !== 'folder')
300 }
301 })
302 } else {
303 serviceOptions.value.forEach((service) => {
304 if (!service.infoItems.some((item) => item.icon === 'folder')) {
305 service.infoItems.splice(0, 0, {
306 icon: 'folder',
307 name: categoryOptions.value.find((cat) => cat.id === service.categoryId).name,
308 })
309 }
310 })
311 }
312
313 if (!serviceCustomizeOptions.value.serviceDuration.visibility) {
314 serviceOptions.value.forEach((service) => {
315 if (service.infoItems.some((item) => item.icon === 'clock')) {
316 service.infoItems = service.infoItems.filter((item) => item.icon !== 'clock')
317 }
318 })
319 } else {
320 serviceOptions.value.forEach((service) => {
321 if (!service.infoItems.some((item) => item.icon === 'clock')) {
322 let position = service.infoItems.findIndex((item) => item.icon === 'folder') !== -1 ? 1 : 0
323 service.infoItems.splice(position, 0, {
324 icon: 'clock',
325 name: durationMap[service.id],
326 })
327 }
328 })
329 }
330
331 if (
332 !serviceCustomizeOptions.value.serviceCapacity.visibility ||
333 licence.isStarter ||
334 licence.isLite
335 ) {
336 serviceOptions.value.forEach((service) => {
337 if (service.infoItems.some((item) => item.icon === 'user')) {
338 service.infoItems = service.infoItems.filter((item) => item.icon !== 'user')
339 }
340 })
341 } else {
342 serviceOptions.value.forEach((service) => {
343 if (!service.infoItems.some((item) => item.icon === 'user')) {
344 let indexC = service.infoItems.findIndex((item) => item.icon === 'folder')
345 let indexD = service.infoItems.findIndex((item) => item.icon === 'clock')
346 let position = indexD < 0 ? (indexC < 0 ? 0 : indexC + 1) : indexD + 1
347 service.infoItems.splice(position, 0, {
348 icon: 'user',
349 name: '1/15',
350 })
351 }
352 })
353 }
354
355 if (
356 !serviceCustomizeOptions.value.serviceLocation.visibility ||
357 licence.isStarter ||
358 licence.isLite
359 ) {
360 serviceOptions.value.forEach((service) => {
361 if (service.infoItems.some((item) => item.icon === 'locations')) {
362 service.infoItems = service.infoItems.filter((item) => item.icon !== 'locations')
363 }
364 })
365 } else {
366 serviceOptions.value.forEach((service) => {
367 if (!service.infoItems.some((item) => item.icon === 'locations')) {
368 let position = service.infoItems.length > 0 ? service.infoItems.length : 0
369 service.infoItems.splice(position, 0, {
370 icon: 'locations',
371 name: locationMap[service.id],
372 })
373 }
374 })
375 }
376 })
377
378 // Methods
379 const selectService = (service) => {
380 selectedService.value = service.id
381 }
382
383 // * Service - More Info
384 let moreInfoVisibility = ref(false)
385
386 let employeesData = ref([
387 {
388 id: 1,
389 firstName: 'James',
390 lastName: 'Miller',
391 pictureThumbPath: '',
392 },
393 {
394 id: 2,
395 firstName: 'Sarah',
396 lastName: 'Johnson',
397 pictureThumbPath: '',
398 },
399 {
400 id: 3,
401 firstName: 'Emily',
402 lastName: 'Davis',
403 pictureThumbPath: '',
404 },
405 {
406 id: 4,
407 firstName: 'Michael',
408 lastName: 'Smith',
409 pictureThumbPath: '',
410 },
411 {
412 id: 5,
413 firstName: 'David',
414 lastName: 'Brown',
415 pictureThumbPath: '',
416 },
417 ])
418
419 let locationsData = ref([
420 {
421 id: 1,
422 name: 'Nail Lounge',
423 address: '123 Nail St, City, Country',
424 pictureThumbPath: '',
425 },
426 {
427 id: 2,
428 name: 'Medical Center',
429 address: '456 Medical Ave, City, Country',
430 pictureThumbPath: '',
431 },
432 {
433 id: 3,
434 name: 'Fitness Gym',
435 address: '789 Fitness Rd, City, Country',
436 pictureThumbPath: '',
437 },
438 ])
439 </script>
440
441 <script>
442 export default {
443 name: 'ServiceStep',
444 key: 'serviceStep',
445 sidebarData: {
446 label: 'service_selection',
447 icon: 'service',
448 stepSelectedData: [],
449 finished: false,
450 selected: false,
451 },
452 }
453 </script>
454