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 / PaymentStep / parts / AppointmentInfo.vue
ameliabooking / v3 / src / views / public / StepForm / PaymentStep / parts Last commit date
AppointmentInfo.vue 1 month ago CartInfo.vue 1 month ago PackageInfo.vue 1 month ago
AppointmentInfo.vue
727 lines
1 <template>
2 <div class="am-fs__payments-wrapper" :style="cssVars">
3 <div class="am-fs__payments">
4 <!-- Recurring Service -->
5 <AmCollapse v-if="isRecurring" class="am-fs__payments-services">
6 <AmCollapseItem
7 :side="true"
8 @collapse-open="recurringCollapseState = false"
9 @collapse-close="recurringCollapseState = true"
10 >
11 <template #heading>
12 <div class="am-fs__payments-services-info">
13 <span>
14 {{ amLabels.summary_services }}
15 </span>
16 </div>
17 </template>
18 <template #icon-below>
19 <Transition
20 :duration="{ enter: 500, leave: 500 }"
21 @enter="onCollapseClose"
22 @leave="onCollapseOpen"
23 >
24 <div v-show="recurringCollapseState" class="am-fs__payments-services-sub">
25 <p>
26 {{ amLabels.summary_services_subtotal }}
27 </p>
28 <p class="am-amount">
29 {{
30 useFormattedPrice(
31 appointmentsAmount.prepaid.totalServiceAmount +
32 appointmentsAmount.postpaid.totalServiceAmount,
33 )
34 }}
35 </p>
36 </div>
37 </Transition>
38 </template>
39 <template #default>
40 <div class="am-fs__payments-services-open">
41 <div class="am-fs__payments-services-open-bordered">
42 <div
43 v-for="(sNum, sPrice) in appointmentsAmount.servicesPrices"
44 :key="sPrice"
45 class="am-fs__payments-services-open-text"
46 >
47 <span>
48 {{
49 `${service.name} (${useFormattedPrice(sPrice)}) x ${persons} ${personsLabel}`
50 }}
51 </span>
52 <span class="am-amount">
53 {{ useFormattedPrice(sPrice * (service.aggregatedPrice ? persons : 1)) }}
54 </span>
55 </div>
56 </div>
57
58 <div class="am-fs__payments-services-open-bordered">
59 <div
60 v-for="(sNum, sPrice) in appointmentsAmount.servicesPrices"
61 v-show="isRecurring"
62 :key="sPrice"
63 class="am-fs__payments-services-open-total am-fs__payments-services-open-text"
64 >
65 <span>
66 {{
67 `${sNum} ${recurrenceLabel(sNum)} x ${service.name} (${useFormattedPrice(sPrice)}) x ${persons} ${personsLabel}`
68 }}
69 </span>
70 </div>
71 </div>
72
73 <div class="am-fs__payments-services-sub">
74 <p>
75 {{ amLabels.summary_services_subtotal }}
76 </p>
77 <p class="am-amount">
78 {{
79 useFormattedPrice(
80 appointmentsAmount.prepaid.totalServiceAmount +
81 appointmentsAmount.postpaid.totalServiceAmount,
82 )
83 }}
84 </p>
85 </div>
86 </div>
87 </template>
88 </AmCollapseItem>
89 </AmCollapse>
90 <!-- /Recurring Service -->
91
92 <div v-else class="am-fs__payments-service">
93 <div class="am-fs__payments-services-info">
94 <span>
95 {{ amLabels.summary_services }}
96 </span>
97 </div>
98 <div class="am-fs__payments-services-open">
99 <div
100 v-for="(sNum, sPrice) in appointmentsAmount.servicesPrices"
101 :key="sPrice"
102 class="am-fs__payments-services-sub"
103 >
104 <p>
105 {{ `${service.name} (${useFormattedPrice(sPrice)})` }}
106 <span v-if="service.aggregatedPrice">
107 {{ `x ${persons} ${personsLabel}` }}
108 </span>
109 </p>
110 <p class="am-amount">
111 {{
112 useFormattedPrice(
113 appointmentsAmount.prepaid.totalServiceAmount +
114 appointmentsAmount.postpaid.totalServiceAmount,
115 )
116 }}
117 </p>
118 </div>
119 </div>
120 </div>
121
122 <AmCollapse v-if="extras.length > 0" class="am-fs__payments-services">
123 <!-- -->
124 <AmCollapseItem
125 :side="true"
126 @collapse-open="extrasCollapseState = false"
127 @collapse-close="extrasCollapseState = true"
128 >
129 <template #heading>
130 <div class="am-fs__payments-services-info">
131 <span>
132 {{ amLabels.summary_extras }}
133 </span>
134 </div>
135 </template>
136 <template #icon-below>
137 <Transition
138 :duration="{ enter: 500, leave: 500 }"
139 @enter="onCollapseClose"
140 @leave="onCollapseOpen"
141 >
142 <div v-show="extrasCollapseState" class="am-fs__payments-services-sub">
143 <p>
144 {{ amLabels.summary_extras_subtotal }}
145 </p>
146 <p class="am-amount">
147 {{
148 useFormattedPrice(
149 appointmentsAmount.prepaid.totalExtrasAmount +
150 appointmentsAmount.postpaid.totalExtrasAmount,
151 )
152 }}
153 </p>
154 </div>
155 </Transition>
156 </template>
157 <template #default>
158 <div class="am-fs__payments-services-open">
159 <div :class="{ 'am-fs__payments-services-open-bordered': isRecurring }">
160 <div
161 v-for="extra in extras"
162 :key="extra.extraId"
163 class="am-fs__payments-services-open-text"
164 >
165 <span>
166 {{ `${extra.quantity} x ${extra.name} (${useFormattedPrice(extra.price)})` }}
167 <span
168 v-if="
169 extra.aggregatedPrice === null
170 ? service.aggregatedPrice
171 : extra.aggregatedPrice
172 "
173 >
174 {{ `x ${persons} ${personsLabel}` }}
175 </span>
176 </span>
177 <span class="am-amount">
178 {{ useFormattedPrice(useAppointmentExtraAmount(service, extra, persons)) }}
179 </span>
180 </div>
181 </div>
182
183 <div class="am-fs__payments-services-open-bordered">
184 <div
185 v-for="(extra, index) in extras"
186 v-show="isRecurring"
187 :key="index"
188 class="am-fs__payments-services-open-total am-fs__payments-services-open-text"
189 >
190 <span>
191 {{
192 `${bookingsCount} ${amLabels.summary_recurrences} x ${extra.name} ( ${useFormattedPrice(useAppointmentExtraAmount(service, extra, persons))} )`
193 }}
194 </span>
195 </div>
196 </div>
197
198 <div class="am-fs__payments-services-sub">
199 <p>
200 {{ amLabels.summary_extras_subtotal }}
201 </p>
202 <p class="am-amount">
203 {{
204 useFormattedPrice(
205 appointmentsAmount.prepaid.totalExtrasAmount +
206 appointmentsAmount.postpaid.totalExtrasAmount,
207 )
208 }}
209 </p>
210 </div>
211 </div>
212 </template>
213 </AmCollapseItem>
214 </AmCollapse>
215 <div v-else-if="extras.length" class="am-fs__payments-extra">
216 <div class="am-fs__payments-services-info">
217 <span>
218 {{ amLabels.summary_extras }}
219 </span>
220 </div>
221 <div class="am-fs__payments-services-open">
222 <div class="am-fs__payments-services-sub">
223 <p>
224 {{
225 `${extras[0].quantity} ${extras[0].name} (${useFormattedPrice(extras[0].price)})`
226 }}
227 <span
228 v-if="
229 extras[0].aggregatedPrice === null
230 ? service.aggregatedPrice
231 : extras[0].aggregatedPrice
232 "
233 >
234 {{ `x ${persons} ${personsLabel}` }}
235 </span>
236 </p>
237 <p class="am-amount">
238 {{
239 useFormattedPrice(
240 appointmentsAmount.prepaid.totalExtrasAmount +
241 appointmentsAmount.postpaid.totalExtrasAmount,
242 )
243 }}
244 </p>
245 </div>
246 </div>
247 </div>
248 </div>
249
250 <div class="am-fs__payments-app-info">
251 <Coupon
252 v-if="settings.payments.coupons"
253 type="appointment"
254 :count="bookingsCount"
255 :ids="[service.id]"
256 @coupon-applied="couponApplied"
257 />
258
259 <div v-if="!onlyTotal" class="am-fs__payments-app-info-subtotal">
260 <span>
261 {{ `${amLabels.subtotal}:` }}
262 </span>
263 <span class="am-amount">
264 {{
265 useFormattedPrice(
266 appointmentsAmount.prepaid.totalAmount + appointmentsAmount.postpaid.totalAmount,
267 )
268 }}
269 </span>
270 </div>
271
272 <Transition name="am-fade">
273 <div
274 v-if="settings.payments.coupons"
275 v-show="appointmentsAmount.prepaid.discountAmount > 0"
276 class="am-fs__payments-app-info-discount"
277 :class="{
278 'am-fs__payments-app-info-discount-green':
279 appointmentsAmount.prepaid.discountAmount > 0,
280 }"
281 >
282 <span>
283 {{ `${amLabels.discount_amount_colon}:` }}
284 </span>
285 <span class="am-amount">
286 {{
287 appointmentsAmount.prepaid.discountAmount +
288 appointmentsAmount.postpaid.discountAmount >
289 appointmentsAmount.prepaid.totalAmount + appointmentsAmount.postpaid.totalAmount
290 ? useFormattedPrice(
291 appointmentsAmount.prepaid.totalAmount +
292 appointmentsAmount.postpaid.totalAmount,
293 )
294 : useFormattedPrice(
295 appointmentsAmount.prepaid.discountAmount +
296 appointmentsAmount.postpaid.discountAmount,
297 )
298 }}
299 </span>
300 </div>
301 </Transition>
302
303 <div
304 v-if="appointmentsAmount.prepaid.taxAmount + appointmentsAmount.postpaid.taxAmount > 0"
305 class="am-fs__payments-app-info-tax"
306 >
307 <span>{{ amLabels.total_tax_colon }}:</span>
308 <span class="am-amount">
309 {{
310 useFormattedPrice(
311 appointmentsAmount.prepaid.taxAmount + appointmentsAmount.postpaid.taxAmount,
312 )
313 }}
314 </span>
315 </div>
316
317 <div
318 class="am-fs__payments-app-info-total"
319 :class="{ 'am-single-row': onlyTotal, 'am-fs__payments-bordered': !onlyTotal }"
320 >
321 <span>{{ amLabels.total_amount_colon }}</span>
322 <span class="am-amount">
323 {{ useFormattedPrice(getTotalAmount()) }}
324 <template v-if="taxVisibility && !amSettings.payments.taxes.excluded">
325 {{ amLabels.incl_tax }}
326 </template>
327 </span>
328 </div>
329
330 <div
331 v-if="
332 (hasDeposit || (isRecurring && appointmentsAmount.postpaid.totalAmount)) &&
333 paymentGateway !== 'onSite'
334 "
335 >
336 <div class="am-fs__payments-app-info-deposit">
337 <span>
338 {{ `${amLabels.paying_now}:` }}
339 </span>
340 <span class="am-amount">
341 {{ useFormattedPrice(getPrepaidAmount()) }}
342 </span>
343 </div>
344
345 <div class="am-fs__payments-app-info-remaining">
346 <span>
347 {{ `${amLabels.paying_later}:` }}
348 </span>
349 <span class="am-amount">
350 {{ useFormattedPrice(getPostpaidAmount()) }}
351 </span>
352 </div>
353 </div>
354 </div>
355 </div>
356 </template>
357
358 <script setup>
359 import Coupon from '../../../Parts/Payment/Coupon.vue'
360 import AmCollapse from '../../../../_components/collapse/AmCollapse'
361 import AmCollapseItem from '../../../../_components/collapse/AmCollapseItem'
362 import { inject, provide, reactive, ref, computed, onMounted } from 'vue'
363 import { useStore } from 'vuex'
364 import { useFormattedPrice } from '../../../../../assets/js/common/formatting.js'
365 import {
366 useAppointmentExtraAmount,
367 useAppointmentsAmountInfo,
368 } from '../../../../../assets/js/common/appointments.js'
369 import { useCartItem } from '../../../../../assets/js/public/cart'
370 import { useColorTransparency } from '../../../../../assets/js/common/colorManipulation.js'
371 import { useTaxVisibility } from '../../../../../assets/js/common/pricing'
372
373 const store = useStore()
374
375 // * Root Settings
376 const amSettings = inject('settings')
377
378 const amLabels = inject('amLabels')
379
380 // * Root Settings
381 const settings = inject('settings')
382
383 let extras = computed(() => store.getters['booking/getSelectedExtras'])
384
385 let persons = computed(() => store.getters['booking/getBookingPersons'])
386
387 let personsLabel = computed(() => {
388 return persons.value > 1 ? amLabels.value.summary_persons : amLabels.value.summary_person
389 })
390
391 function recurrenceLabel(num) {
392 return num > 1 ? amLabels.value.summary_recurrences : amLabels.value.summary_recurrence
393 }
394
395 let isRecurring = ref(false)
396
397 let hasDeposit = inject('hasDeposit')
398
399 let appointmentsAmount = computed(() => {
400 let amountInfo = useAppointmentsAmountInfo(store)
401
402 let amount = {
403 prepaid: {
404 totalAmount: 0,
405 totalServiceAmount: 0,
406 totalExtrasAmount: 0,
407 discountAmount: 0,
408 taxAmount: 0,
409 depositAmount: 0,
410 },
411 postpaid: {
412 totalAmount: 0,
413 totalServiceAmount: 0,
414 totalExtrasAmount: 0,
415 discountAmount: 0,
416 taxAmount: 0,
417 depositAmount: 0,
418 },
419 }
420
421 amountInfo.forEach((item) => {
422 amount.prepaid.totalAmount += item.prepaid.totalAmount
423 amount.postpaid.totalAmount += item.postpaid.totalAmount
424
425 amount.prepaid.totalServiceAmount += item.prepaid.totalServiceAmount
426 amount.postpaid.totalServiceAmount += item.postpaid.totalServiceAmount
427
428 amount.prepaid.totalExtrasAmount += item.prepaid.totalExtrasAmount
429 amount.postpaid.totalExtrasAmount += item.postpaid.totalExtrasAmount
430
431 amount.prepaid.discountAmount += item.prepaid.discountAmount
432 amount.postpaid.discountAmount += item.postpaid.discountAmount
433
434 amount.prepaid.taxAmount += item.prepaid.taxAmount
435 amount.postpaid.taxAmount += item.postpaid.taxAmount
436
437 amount.prepaid.depositAmount += item.prepaid.depositAmount
438 amount.postpaid.depositAmount += item.postpaid.depositAmount
439
440 amount.servicesPrices = item.servicesPrices
441
442 amount.prepaid.count = item.prepaid.count
443 amount.postpaid.count = item.postpaid.count
444 })
445
446 return amount
447 })
448
449 function getPrepaidAmount() {
450 const { totalAmount, discountAmount, taxAmount, depositAmount } = appointmentsAmount.value.prepaid
451 const total = totalAmount - discountAmount + taxAmount
452
453 return depositAmount ? Math.min(total, depositAmount) : total
454 }
455
456 function getPostpaidAmount() {
457 return getTotalAmount() - getPrepaidAmount()
458 }
459
460 function getTotalAmount() {
461 return (
462 appointmentsAmount.value.prepaid.totalAmount +
463 appointmentsAmount.value.postpaid.totalAmount +
464 appointmentsAmount.value.prepaid.taxAmount +
465 appointmentsAmount.value.postpaid.taxAmount -
466 appointmentsAmount.value.prepaid.discountAmount -
467 appointmentsAmount.value.postpaid.discountAmount
468 )
469 }
470
471 let onlyTotal = computed(() => {
472 return (
473 (!settings.payments.coupons || appointmentsAmount.value.prepaid.discountAmount === 0) &&
474 (!settings.payments.taxes.enabled ||
475 appointmentsAmount.value.prepaid.taxAmount + appointmentsAmount.value.postpaid.taxAmount ===
476 0)
477 )
478 })
479
480 let paymentGateway = computed(() => store.getters['booking/getPaymentGateway'])
481
482 let bookingsCount = ref(0)
483
484 provide('bookingsCount', bookingsCount)
485
486 let service = reactive(store.getters['entities/getService'](useCartItem(store).serviceId))
487
488 const emits = defineEmits(['setOnSitePayment'])
489
490 function couponApplied() {
491 emits(
492 'setOnSitePayment',
493 appointmentsAmount.value.prepaid.totalAmount -
494 appointmentsAmount.value.prepaid.discountAmount +
495 appointmentsAmount.value.prepaid.taxAmount <=
496 0,
497 )
498 }
499 onMounted(() => {
500 isRecurring.value =
501 appointmentsAmount.value.prepaid.count > 1 || appointmentsAmount.value.postpaid.count > 0
502
503 bookingsCount.value =
504 appointmentsAmount.value.prepaid.count + appointmentsAmount.value.postpaid.count
505
506 store.commit('booking/setBookingsCount', bookingsCount.value)
507 })
508
509 // * Collapse visualisation
510 let recurringCollapseState = ref(true)
511 let extrasCollapseState = ref(true)
512
513 function onCollapseClose(el) {
514 el.style.opacity = 0
515 setTimeout(() => {
516 el.style.opacity = 1
517 el.style.height = 'var(--am-h-services-sub)'
518 }, 200)
519 }
520
521 function onCollapseOpen(el) {
522 el.style.opacity = 0
523 el.style.setProperty('--am-h-services-sub', `${el.offsetHeight}px`)
524 setTimeout(() => {
525 el.style.height = '0px'
526 }, 100)
527 }
528
529 let taxVisibility = computed(() => {
530 return useTaxVisibility(store, service.id, 'service')
531 })
532
533 // * Colors
534 let amColors = inject('amColors')
535 let cssVars = computed(() => {
536 return {
537 '--am-c-pay-text': amColors.value.colorMainText,
538 '--am-c-pay-text-op70': useColorTransparency(amColors.value.colorMainText, 0.7),
539 '--am-c-pay-text-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
540 '--am-c-pay-text-op30': useColorTransparency(amColors.value.colorMainText, 0.3),
541 '--am-c-pay-success': amColors.value.colorSuccess,
542 '--am-c-pay-primary': amColors.value.colorPrimary,
543 }
544 })
545 </script>
546
547 <script>
548 export default {
549 name: 'AppointmentInfo',
550 }
551 </script>
552
553 <style lang="scss">
554 .amelia-v2-booking {
555 #amelia-container {
556 .am-fs__payments {
557 &-bordered {
558 border-top: 1px dashed var(--am-c-pay-text-op30);
559 }
560
561 &-app-info {
562 margin: 0;
563
564 &-subtotal {
565 display: flex;
566 justify-content: space-between;
567 margin-top: 16px;
568
569 span {
570 font-size: 14px;
571 font-weight: 500;
572 line-height: 1.42857;
573 color: var(--am-c-pay-text);
574 }
575 }
576
577 &-discount,
578 &-tax {
579 display: flex;
580 justify-content: space-between;
581 font-size: 13px;
582 font-weight: 400;
583 line-height: 1.3846;
584 color: var(--am-c-pay-text);
585 padding: 8px 0 0;
586
587 &-green {
588 color: var(--am-c-pay-success);
589 }
590 }
591
592 &-discount {
593 &-green {
594 color: var(--am-c-pay-success);
595 }
596 }
597
598 &-total {
599 margin-top: 8px;
600 }
601
602 &-total,
603 &-deposit,
604 &-remaining {
605 display: flex;
606 justify-content: space-between;
607 font-size: 15px;
608 font-weight: 500;
609 line-height: 1.33333;
610 color: var(--am-c-pay-text);
611 padding: 8px 0 0;
612
613 &.am-single-row {
614 padding-top: 16px;
615 }
616
617 & > span:nth-child(2) {
618 color: var(--am-c-pay-primary);
619 }
620 }
621 }
622
623 &-service,
624 &-extra {
625 border: 1px solid var(--am-c-pay-text-op30);
626 border-radius: 8px;
627 padding: 12px;
628 box-sizing: border-box;
629 }
630
631 &-extra {
632 margin: 8px 0;
633 }
634
635 &-services {
636 .am-collapse-item__heading {
637 flex-wrap: wrap;
638 width: 100%;
639 padding: 12px;
640 transition-delay: 0.5s;
641
642 &-side {
643 transition-delay: 0s;
644 }
645
646 .am-collapse-item__trigger {
647 padding: 0;
648 }
649 }
650 .am-collapse-item__content {
651 & > * {
652 padding: 12px;
653 }
654 }
655
656 &-info {
657 span {
658 font-size: 12px;
659 line-height: 1.33333;
660 font-weight: 500;
661 color: var(--am-c-pay-text-op60);
662 }
663 }
664
665 &-sub {
666 width: 100%;
667 display: flex;
668 justify-content: space-between;
669 transition: all ease-in-out 0.3s;
670
671 p {
672 font-size: 13px;
673 font-weight: 500;
674 line-height: 1.384615;
675 color: var(--am-c-pay-text);
676 margin: 0;
677 padding: 0;
678 }
679 }
680
681 &-open {
682 width: 100%;
683
684 &-bordered {
685 border-bottom: 1px dashed var(--am-c-pay-text-op30);
686 margin-bottom: 10px;
687 padding-bottom: 10px;
688 }
689
690 &-total {
691 & > span {
692 color: var(--am-c-pay-text-op70);
693 }
694
695 &:last-child {
696 font-weight: 500;
697 }
698 }
699
700 &-text {
701 display: flex;
702 justify-content: space-between;
703 font-size: 13px;
704 line-height: 1.384615;
705 color: var(--am-c-pay-text);
706 }
707 }
708 }
709
710 .am-amount {
711 white-space: nowrap;
712 }
713
714 .am-fade-enter-active,
715 .am-fade-leave-active {
716 transition: opacity 0.5s ease;
717 }
718
719 .am-fade-enter-from,
720 .am-fade-leave-to {
721 opacity: 0;
722 }
723 }
724 }
725 }
726 </style>
727