PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.20
Booking for Appointments and Events Calendar – Amelia v1.2.20
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 / InitStep / InitStep.vue
ameliabooking / v3 / src / views / public / StepForm / InitStep Last commit date
InitStep.vue 1 year ago
InitStep.vue
569 lines
1 <template>
2 <div
3 v-if="loaded"
4 class="am-fs__init"
5 :class="[props.globalClass, {'am-oxvisible': (bringingAnyoneVisibility || packagesVisibility)}]"
6 >
7 <el-form
8 ref="initFormRef"
9 :model="initFormData"
10 :rules="rules"
11 label-position="top"
12 class="am-fs__init-form"
13 >
14 <template v-for="field in amCustomize.initStep.order" :key="field.id">
15 <component
16 :is="amFields[field.id].template"
17 v-bind="amFields[field.id].props"
18 ></component>
19 </template>
20 </el-form>
21
22 <!-- Bringing Anyone with you -->
23 <AmSlidePopup
24 v-if="bringingAnyoneOptions.availability"
25 :visibility="bringingAnyoneVisibility"
26 class="am-fs__init__bringing"
27 >
28 <p class="am-fs__popup-x" @click="closeBringingPopup">
29 <AmeliaIconClose></AmeliaIconClose>
30 </p>
31 <BringingAnyone :in-popup="true"></BringingAnyone>
32 <template #footer>
33 <AmButton
34 v-if="(bringingAnyoneOptions.min !== bringingAnyoneOptions.max) && bringingAnyoneOptions.min <= 0"
35 category="secondary"
36 :type="amCustomize.bringingAnyone.options.secondaryButton.buttonType"
37 :disabled="(bringingAnyoneOptions.min === bringingAnyoneOptions.max) || (!amSettings.appointments.allowBookingIfNotMin && bringingAnyoneOptions.min > 0)"
38 @click="noOneBringWith"
39 >
40 {{bringingLabels.bringing_no}}
41 </AmButton>
42 <AmButton
43 :type="(bringingAnyoneOptions.min !== bringingAnyoneOptions.max) && bringingAnyoneOptions.min <= 0 ?
44 amCustomize.bringingAnyone.options.primaryButton.buttonType : amCustomize.bringingAnyone.options.primaryFooterButton.buttonType"
45 :disabled="bookingPersons === (amSettings.appointments.bringingAnyoneLogic === 'additional' ? 1 : 0)"
46 @click="bringPeopleWithYou"
47 >
48 <template v-if="(bringingAnyoneOptions.min !== bringingAnyoneOptions.max) && bringingAnyoneOptions.min <= 0">
49 {{bringingLabels.bringing_yes}}
50 </template>
51 <template v-else>
52 {{bringingLabels.continue}}
53 </template>
54 </AmButton>
55 </template>
56 </AmSlidePopup>
57 <!--/ Bringing Anyone with you -->
58
59 <!-- Packages Popup -->
60 <PackagesPopup class="am-fs__init__package" @continue-with-service="continueWithService()"></PackagesPopup>
61 <!--/ Packages Popup -->
62 </div>
63
64 <!-- Skeleton -->
65 <el-skeleton v-else animated :style="cssPackage">
66 <template #template>
67 <div v-for="item in new Array(3)" :key="item">
68 <div class="el-skeleton-item-wrapper">
69 <el-skeleton-item variant="h3" />
70 <el-skeleton-item variant="text" />
71 </div>
72 </div>
73 </template>
74 </el-skeleton>
75 <!-- /Skeleton -->
76 </template>
77
78 <script setup>
79 import { useStore } from "vuex";
80 import {ref, reactive, computed, watchEffect, inject, provide, markRaw} from 'vue'
81
82 import AmSlidePopup from "../../../_components/slide-popup/AmSlidePopup.vue";
83 import AmButton from "../../../_components/button/AmButton.vue";
84 import BringingAnyone from "../BringingAnyone/BringingAnyone.vue";
85 import AmeliaIconClose from "../../../_components/icons/IconClose";
86 import PackagesPopup from "../PakagesStep/parts/PackagesPopup";
87
88 import ServiceFormField from "../_fields/ServiceFormField.vue";
89 import LocationFormField from "../_fields/LocationFormField.vue";
90 import EmployeeFormField from "../_fields/EmployeeFormField.vue";
91
92 import { useColorTransparency } from "../../../../assets/js/common/colorManipulation";
93 import { useCapacity } from "../../../../assets/js/common/appointments";
94 import { useCart } from '../../../../assets/js/public/cart'
95
96 let props = defineProps({
97 globalClass: {
98 type: String,
99 default: ''
100 }
101 })
102
103 let amCustomize = inject('amCustomize')
104
105 // * Amelia Settings
106 const amSettings = inject('settings')
107
108 function isFieldFilterable(key) {
109 return (amCustomize.initStep.options[key] &&
110 'filterable' in amCustomize.initStep.options[key]) ?
111 amCustomize.initStep.options[key].filterable : true
112 }
113
114 let amFields = reactive({
115 service: {
116 template: markRaw(ServiceFormField),
117 props: {
118 filterable: isFieldFilterable('service'),
119 taxVisible: amCustomize.initStep.options.tax?.visibility ?? true
120 }
121 },
122 location: {
123 template: markRaw(LocationFormField),
124 props: {
125 visibility: amCustomize.initStep.options.location.visibility,
126 filterable: isFieldFilterable('location')
127 }
128 },
129 employee: {
130 template: markRaw(EmployeeFormField),
131 props: {
132 visibility: amCustomize.initStep.options.employee.visibility,
133 filterable: isFieldFilterable('employee')
134 }
135 }
136 })
137
138 // * Store
139 let store = useStore();
140
141 let loaded = computed(() => {
142 return store.getters['entities/getReady']
143 })
144
145 let bookingPersons = computed(() => {
146 return store.getters['booking/getBookingPersons']
147 })
148
149 // * Short Code
150 const shortcodeData = inject('shortcodeData')
151
152 // * Labels
153 const globalLabels = inject('labels')
154
155 // * local language short code
156 const localLanguage = inject('localLanguage')
157
158 // * if local lang is in settings lang
159 let langDetection = computed(() => amSettings.general.usedLanguages.includes(localLanguage.value))
160
161 // * Computed labels
162 let amLabels = computed(() => {
163 let computedLabels = reactive({...globalLabels})
164
165 if (amSettings.customizedData && amSettings.customizedData.sbsNew && amSettings.customizedData.sbsNew.initStep.translations) {
166 let customizedLabels = amSettings.customizedData.sbsNew.initStep.translations
167 Object.keys(customizedLabels).forEach(labelKey => {
168 if (customizedLabels[labelKey][localLanguage.value] && langDetection.value) {
169 computedLabels[labelKey] = customizedLabels[labelKey][localLanguage.value]
170 } else if (customizedLabels[labelKey].default) {
171 computedLabels[labelKey] = customizedLabels[labelKey].default
172 }
173 })
174 }
175 return computedLabels
176 })
177
178 provide('amLabels', amLabels)
179
180 let bringingLabels = computed(() => {
181 let computedLabels = reactive({...globalLabels})
182
183 if (amSettings.customizedData && amSettings.customizedData.sbsNew && amSettings.customizedData.sbsNew.bringingAnyone.translations) {
184 let customizedLabels = amSettings.customizedData.sbsNew.bringingAnyone.translations
185 Object.keys(customizedLabels).forEach(labelKey => {
186 if (customizedLabels[labelKey][localLanguage.value] && langDetection.value) {
187 computedLabels[labelKey] = customizedLabels[labelKey][localLanguage.value]
188 } else if (customizedLabels[labelKey].default) {
189 computedLabels[labelKey] = customizedLabels[labelKey].default
190 }
191 })
192 }
193 return computedLabels
194 })
195
196 // * Step Functions
197 const { nextStep, footerButtonReset, footerButtonClicked } = inject('changingStepsFunctions', {
198 nextStep: () => {},
199 footerButtonReset: () => {},
200 footerButtonClicked: {
201 value: false
202 }
203 })
204
205 /**
206 * * Bringing anyone with you - block
207 */
208
209 let bringingAnyoneOptions = computed(() => {
210 return useCapacity(store.getters['entities/getEmployeeServices'](
211 store.getters['booking/getServiceProviderSelection']
212 ))
213 })
214
215 provide('bringingOptions', {
216 bringingAnyoneOptions
217 })
218
219 // * Bringing anyone with you popup visibility
220 let bringingAnyoneVisibility = ref(false)
221
222 function noOneBringWith() {
223 closeBringingPopup()
224 store.commit('booking/setBookingPersons', 0)
225 nextStep()
226 }
227
228 function bringPeopleWithYou() {
229 nextStep()
230 }
231
232 function continueWithService() {
233 packagesVisibility.value = false
234 if (bringingAnyoneOptions.value.availability) {
235 bringingAnyoneVisibility.value = true
236 } else {
237 nextStep()
238 }
239 }
240
241 function closeBringingPopup () {
242 bringingAnyoneVisibility.value = false
243 }
244
245 // * Package popup
246 let packagesOptions = computed(() => store.getters['entities/filteredPackages'](
247 store.getters['booking/getSelection']
248 ))
249
250 let packagesVisibility = ref(false)
251
252 provide('packagesVisibility', packagesVisibility)
253
254
255 /**
256 * Form Block start
257 */
258 // * Form reference
259 let initFormRef = ref(null)
260
261 // * Form data
262 let initFormData = ref({
263 service: computed({
264 get: () => {
265 if (store.getters['booking/getCategoryId'] === null && store.getters['booking/getServiceId'] === null) {
266 return null
267 }
268 return [
269 store.getters['booking/getCategoryId'],
270 store.getters['booking/getServiceId']
271 ]
272 },
273 set: (val) => {
274 store.commit('booking/setCategoryId', val ? val[0] : null)
275 store.commit('booking/setServiceId', val ? val[1] : null)
276 store.commit('booking/setBookableType', 'appointment')
277 }
278 }),
279 onlyService: computed({
280 get: () => store.getters['booking/getServiceId'],
281 set: (val) => {
282 store.commit('booking/setServiceId', val ? val : null)
283 store.commit('booking/setBookableType', 'appointment')
284 }
285 }),
286 location: computed({
287 get: () => store.getters['booking/getLocationId'],
288 set: (val) => {
289 store.commit('booking/setLocationId', val ? val : null)
290 }
291 }),
292 employee: computed({
293 get: () => store.getters['booking/getEmployeeId'],
294 set: (val) => {
295 // let employee = employeeOptions.value.find(item => item.id === val)
296 // store.commit('booking/setEmployeeId', employee ? employee.id : null)
297 store.commit('booking/setEmployeeId', val ? val : null)
298 }
299 })
300 })
301
302 provide('initFormData', initFormData)
303
304 // * Form validation rules
305 let rules = ref({
306 service: [
307 {
308 required: true,
309 message: amLabels.value.please_select_service,
310 trigger: ['blur', 'change'],
311 }
312 ],
313 onlyService: [
314 {
315 required: true,
316 message: amLabels.value.please_select_service,
317 trigger: ['blur', 'change'],
318 }
319 ],
320 location: [
321 {
322 required: amCustomize.initStep.options.location.required,
323 message: amLabels.value.please_select_location,
324 trigger: ['blur', 'change'],
325 }
326 ],
327 employee: [
328 {
329 required: amCustomize.initStep.options.employee.required,
330 message: amLabels.value.please_select_employee,
331 trigger: ['blur', 'change'],
332 }
333 ],
334 })
335
336 /**
337 * Submit Form Function
338 */
339 function submitForm() {
340 initFormRef.value.validate((valid) => {
341 footerButtonReset()
342 if (valid) {
343 if (packagesOptions.value.length && shortcodeData.value.show !== 'services' && useCart(store).length <= 1) {
344 packagesVisibility.value = true
345 } else {
346 continueWithService()
347 }
348 } else {
349 return false
350 }
351 })
352 }
353
354 // * Watching when footer button was clicked
355 watchEffect(() => {
356 if (footerButtonClicked.value) {
357 submitForm()
358 }
359 })
360
361 // * Colors
362 let amColors = inject('amColors')
363 let cssPackage = computed(() => {
364 return {
365 '--am-c-ps-text-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
366 '--am-c-ps-text-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
367 }
368 })
369 </script>
370
371 <script>
372 export default {
373 name: 'InitStep',
374 key: 'initStep',
375 sidebarData: {
376 label: 'service_selection',
377 icon: 'service',
378 stepSelectedData: [],
379 finished: false,
380 selected: false,
381 }
382 }
383 </script>
384
385 <style lang="scss">
386 .amelia-v2-booking #amelia-container {
387 .am-fs {
388 &__main {
389 &-content.am-fs__init {
390 padding-top: 40px;
391
392 &.am-oxvisible {
393 overflow-x: visible;
394 }
395 }
396
397 &-inner {
398 overflow: hidden;
399 }
400 }
401
402 &__init {
403 &-form {
404 &__item {
405 $count: 100;
406 @for $i from 0 through $count {
407 &:nth-child(#{$i + 1}) {
408 animation: 600ms cubic-bezier(.45,1,.4,1.2) #{$i*100}ms am-animation-slide-up;
409 animation-fill-mode: both;
410 }
411 }
412
413 &.el-form-item {
414 margin-bottom: 24px;
415 }
416 }
417
418 &__label {
419 display: inline-block;
420 font-family: var(--am-font-family);
421 font-weight: 500;
422 color: var(--am-c-main-text);
423 margin-bottom: 4px;
424 }
425
426 .el-form-item {
427 &__label {
428 color: var(--am-c-main-text);
429 line-height: unset;
430 padding: 0;
431
432 &:before {
433 color: var(--am-c-error);
434 }
435 }
436
437 &__error {
438 color: var(--am-c-error);
439 }
440 }
441 }
442 }
443
444 &__popup-x {
445 position: absolute;
446 top:16px;
447 right:16px;
448 cursor: pointer;
449 color: var(--am-c-main-text);
450 margin: 0;
451 }
452
453 &__ps {
454 &-popup {
455 position: relative;
456
457 &__heading {
458 font-size: 14px;
459 font-weight: 400;
460 line-height: 1.42857;
461 text-align: center;
462 color: var(--am-c-main-text);
463 margin: 0 0 16px;
464 padding: 20px 6px 0 0;
465 max-height: 40px;
466 overflow-x: hidden;
467
468 // Main Scroll styles
469 &::-webkit-scrollbar {
470 width: 6px;
471 }
472
473 &::-webkit-scrollbar-thumb {
474 border-radius: 6px;
475 background: var(--am-c-scroll-op30);
476 }
477
478 &::-webkit-scrollbar-track {
479 border-radius: 6px;
480 background: var(--am-c-scroll-op10);
481 }
482 }
483
484 &__or {
485 display: flex;
486 flex-direction: row;
487 font-weight: 400;
488 font-size: 14px;
489 line-height: 20px;
490 margin: 20px 0;
491 color: var(--am-c-ps-text-op60);
492
493 &:before, &:after{
494 content: "";
495 flex: 1 1;
496 border-bottom: 1px solid var(--am-c-ps-text-op20);
497 margin: auto;
498 }
499
500 &:before {
501 margin-right: 10px
502 }
503
504 &:after {
505 margin-left: 10px
506 }
507 }
508
509 &__btn {
510 &.am-button.am-button--medium {
511 --am-h-btn: 56px;
512 --am-fs-btn: 14px;
513 line-height: 16px;
514 width: 100%;
515 justify-content: space-between;
516 }
517
518 &-mobile {
519 &.am-button.am-button--medium {
520 --am-fs-btn: 12px
521 }
522 }
523 }
524 }
525
526 &-pill {
527 display: inline-block;
528 font-size: 14px;
529 font-weight: 500;
530 line-height: 1;
531 color: var(--am-c-btn-prim);
532 background-color: var(--am-c-btn-prim-text);
533 border-radius: 12px;
534 padding: 5px 8px;
535 }
536 }
537 }
538
539 .el-skeleton {
540 width: 100%;
541 padding: 16px 32px;
542
543 &.skeleton-mobile {
544 .el-skeleton {
545 padding: 16px;
546 }
547 }
548
549 &-item-wrapper {
550 padding: 8px 0;
551 display: flex;
552 align-items: flex-start;
553 flex-direction: column;
554
555 .el-skeleton__h3 {
556 margin-bottom: 4px;
557 width: 100px;
558 height: 20px;
559 }
560
561 .el-skeleton__text {
562 width: 100%;
563 height: 40px;
564 }
565 }
566 }
567 }
568 </style>
569