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 / admin / customize / steps / Cabinet / common / Events / Events.vue
ameliabooking / v3 / src / views / admin / customize / steps / Cabinet / common / Events Last commit date
Events.vue 1 month ago
Events.vue
697 lines
1 <template>
2 <div ref="pageContainer" class="am-cap" :style="cssVars">
3 <CabinetFilters :param-list="paramList" :responsive-class="responsiveClass" />
4
5 <div v-if="pageRenderKey === 'cape'" class="am-cap__actions" :class="responsiveClass">
6 <AmButton
7 v-if="features.eTickets && amCustomize.cape.events.options.scanQrCodeBtn.visibility"
8 prefix="scan-qr-code"
9 size="small"
10 category="secondary"
11 type="plain"
12 >
13 {{ amLabels.scan_e_ticket }}
14 </AmButton>
15 <AmButton
16 prefix="plus"
17 size="small"
18 category="primary"
19 :type="amCustomize.cape.events.options.newEvtBtn.buttonType"
20 >
21 <span>{{ amLabels.new_event }}</span>
22 </AmButton>
23 </div>
24
25 <div
26 class="am-cape__wrapper"
27 :class="[
28 {
29 'am-no-border': dateGroupedEvents && Object.keys(dateGroupedEvents).length === 1,
30 },
31 responsiveClass,
32 ]"
33 >
34 <div v-for="(item, dateKey) in dateGroupedEvents" :key="dateKey" class="am-cape">
35 <div
36 class="am-cape__date"
37 :class="[
38 {
39 'am-today':
40 getFrontedFormattedDate(dateKey) ===
41 getFrontedFormattedDate(moment().format('YYYY-MM-DD')),
42 },
43 {
44 'am-no-flag': dateGroupedEvents && Object.keys(dateGroupedEvents).length === 1,
45 },
46 responsiveClass,
47 ]"
48 >
49 {{ getFrontedFormattedDate(dateKey) }}
50 </div>
51 <CollapseCard
52 v-for="(event, index) in item.events"
53 :key="index"
54 :start="getFrontedFormattedTime(event.periods[0].periodStart.split(' ')[1].slice(0, 5))"
55 :name="event.name"
56 :employee="[{ firstName: 'John', lastName: 'Doe', rank: 'organizer' }]"
57 :price="useEventBookingsPrice(event)"
58 :duration="null"
59 :periods="(periods = usePeriodsData(event.periods)) ? periods : []"
60 :extras="[]"
61 :tickets="event.customTickets"
62 :custom-fields="event.bookings[0].customFields"
63 :location="event.location"
64 :google-meet-link="event.periods[0].googleMeetUrl"
65 :microsoft-teams-link="event.periods[0].microsoftTeamsUrl"
66 :zoom-link="event.periods[0].zoomMeeting.joinUrl"
67 :lesson-space-link="event.periods[0].lessonSpace"
68 :bookable="event"
69 :reservation="event"
70 :booking="event.bookings[0]"
71 :responsive-class="responsiveClass"
72 :qr-codes="qrCodesData"
73 :parent-width="pageWidth"
74 :customized-options="amCustomize[pageRenderKey][stepName].options"
75 ></CollapseCard>
76 </div>
77 </div>
78
79 <CancelPopup
80 v-if="cancelVisibility"
81 :visibility="cancelVisibility"
82 title-key="cancel_event"
83 content-key="confirm_cancel_event"
84 >
85 </CancelPopup>
86 </div>
87 </template>
88
89 <script setup>
90 // * Import from libraries
91 import moment from 'moment'
92
93 // * import from Vue
94 import { ref, computed, inject, onMounted, watch, nextTick } from 'vue'
95
96 // * Templates
97 import CancelPopup from '../parts/CancelPopup.vue'
98 import CollapseCard from '../parts/CollapseCard/CollapseCard.vue'
99 import CabinetFilters from '../parts/Filters.vue'
100
101 // * Composables
102 import {
103 getFrontedFormattedDate,
104 getFrontedFormattedTime,
105 } from '../../../../../../../assets/js/common/date'
106 import { useEventBookingsPrice, usePeriodsData } from '../../../../../../../assets/js/admin/event'
107 import { useResponsiveClass } from '../../../../../../../assets/js/common/responsive'
108 import { useColorTransparency } from '../../../../../../../assets/js/common/colorManipulation'
109 import { useReactiveCustomize } from '../../../../../../../assets/js/admin/useReactiveCustomize.js'
110
111 // * Components
112 import AmButton from '../../../../../../_components/button/AmButton.vue'
113
114 let pageRenderKey = inject('pageRenderKey')
115
116 // * Global Settings
117 let amSettings = inject('settings')
118
119 // * Plugin Licence
120 let licence = inject('licence')
121
122 // * Features
123 let features = inject('features')
124
125 // * Page Content width
126 let pageContainer = ref(null)
127 let pageWidth = ref(0)
128
129 // * Global labels
130 let amLabels = inject('labels')
131
132 // * Sidebar collapsed var
133 let sidebarCollapsed = inject('sidebarCollapsed')
134
135 // * window resize listener
136 window.addEventListener('resize', resize)
137 // * resize function
138 function resize() {
139 if (pageContainer.value) {
140 pageWidth.value = pageContainer.value.offsetWidth
141 }
142 }
143
144 watch(sidebarCollapsed, (current) => {
145 if (current) {
146 setTimeout(() => {
147 collapseTriggered()
148 }, 1500)
149 } else {
150 setTimeout(() => {
151 collapseTriggered()
152 }, 500)
153 }
154 })
155
156 function collapseTriggered() {
157 pageWidth.value = pageContainer.value.offsetWidth
158 }
159
160 onMounted(() => {
161 nextTick(() => {
162 pageWidth.value = pageContainer.value.offsetWidth
163 })
164 })
165
166 let responsiveClass = computed(() => {
167 return useResponsiveClass(pageWidth.value)
168 })
169
170 /********
171 * Form *
172 ********/
173 let paramList = ref({
174 dates: {
175 name: 'dates',
176 },
177 events: {
178 name: 'events',
179 icon: 'event',
180 ids: [],
181 },
182 providers: {
183 name: 'providers',
184 icon: 'employee',
185 ids: [],
186 },
187 locations: {
188 name: 'locations',
189 icon: 'locations',
190 ids: [],
191 },
192 })
193
194 if (licence.isStarter) {
195 delete paramList.value.locations
196 }
197
198 if (pageRenderKey.value === 'cape') {
199 delete paramList.value.providers
200 paramList.value.customers = {
201 name: 'customers',
202 icon: 'user',
203 ids: [],
204 }
205 }
206
207 let arrApp = [
208 {
209 when: 0,
210 name: 'Healthy Plate Symposium',
211 status: 'approved',
212 },
213 {
214 when: 0,
215 name: 'Nutrition Nirvana Workshop',
216 status: 'canceled',
217 },
218 {
219 when: 2,
220 name: 'Healthy Plate Symposium',
221 status: 'approved',
222 },
223 {
224 when: 2,
225 name: 'Culinary Wellness Cooking Class',
226 status: 'canceled',
227 },
228 {
229 when: 5,
230 name: 'Nutrition Nirvana Workshop',
231 status: 'rejected',
232 },
233 {
234 when: 5,
235 name: 'Healthy Plate Symposium',
236 status: 'approved',
237 },
238 {
239 when: 5,
240 name: 'Nutrition Nirvana Workshop',
241 status: 'approved',
242 },
243 ]
244
245 let dateGroupedEvents = ref({})
246
247 arrApp.forEach((item, index) => {
248 if (
249 Object.keys(dateGroupedEvents.value).indexOf(
250 moment().add(item.when, 'days').format('YYYY-MM-DD'),
251 ) < 0
252 ) {
253 dateGroupedEvents.value[moment().add(item.when, 'days').format('YYYY-MM-DD')] = {}
254 dateGroupedEvents.value[moment().add(item.when, 'days').format('YYYY-MM-DD')].date = moment()
255 .add(item.when, 'days')
256 .format('YYYY-MM-DD')
257 dateGroupedEvents.value[moment().add(item.when, 'days').format('YYYY-MM-DD')].events = []
258 }
259
260 let app = {
261 aggregatedPrice: true,
262 bookMultipleTimes: true,
263 bookable: true,
264 bookingCloses: null,
265 bookingClosesRec: 'same',
266 bookingOpens: null,
267 bookingOpensRec: 'same',
268 bookings: [
269 {
270 actionsCompleted: null,
271 aggregatedPrice: true,
272 appointmentId: null,
273 coupon: null,
274 couponId: null,
275 created: null,
276 customFields: [
277 { label: 'Custom field 1', value: '' },
278 { label: 'Custom field 2', value: '' },
279 { label: 'Custom field 3', value: '' },
280 ],
281 customer: {
282 birthday: null,
283 countryPhoneIso: null,
284 email: 'testjanedoe@test.com',
285 externalId: null,
286 firstName: 'Jane',
287 gender: null,
288 id: 1,
289 lastName: 'Doe',
290 note: null,
291 phone: '',
292 pictureFullPath: null,
293 pictureThumbPath: null,
294 status: 'visible',
295 translations: null,
296 type: 'customer',
297 zoomUserId: null,
298 },
299 customerId: 1,
300 duration: null,
301 extras: [],
302 id: 1,
303 info: '{"firstName":"Jane","lastName":"Doe","phone":null,"locale":"en_US","timeZone":"Europe\\/Belgrade","urlParams":null}',
304 isChangedStatus: null,
305 isLastBooking: null,
306 isUpdated: null,
307 packageCustomerService: null,
308 payments: [],
309 persons: 1,
310 price: 0,
311 status: item.status,
312 ticketsData: [
313 {
314 customerBookingId: index,
315 eventTicketId: index,
316 id: index,
317 persons: 2,
318 price: 10,
319 },
320 {
321 customerBookingId: index,
322 eventTicketId: index,
323 id: index + 1,
324 persons: 1,
325 price: 15,
326 },
327 {
328 customerBookingId: index,
329 eventTicketId: index,
330 id: index + 2,
331 persons: 3,
332 price: 20,
333 },
334 ],
335 token: null,
336 utcOffset: null,
337 },
338 ],
339 bringingAnyone: true,
340 cancelable: true,
341 closeAfterMin: null,
342 closeAfterMinBookings: false,
343 closed: false,
344 color: '#FA3C52',
345 coupons: [],
346 created: '2023-02-23 11:08:33',
347 customLocation: null,
348 customPricing: true,
349 customTickets: [
350 {
351 dateRangePrice: null,
352 dateRanges: '[]',
353 enabled: true,
354 eventId: index,
355 id: index,
356 name: 'Ticket 1',
357 price: 10,
358 sold: 2,
359 spots: 60,
360 persons: 2,
361 translations: null,
362 },
363 {
364 dateRangePrice: null,
365 dateRanges: '[]',
366 enabled: true,
367 eventId: index,
368 id: index + 1,
369 name: 'Ticket 2',
370 price: 15,
371 sold: 1,
372 spots: 60,
373 persons: 1,
374 translations: null,
375 },
376 {
377 dateRangePrice: null,
378 dateRanges: '[]',
379 enabled: true,
380 eventId: index,
381 id: index + 2,
382 name: 'Ticket 3',
383 price: 20,
384 sold: 3,
385 spots: 60,
386 persons: 3,
387 translations: null,
388 },
389 ],
390 deposit: 10,
391 depositPayment: 'fixed',
392 depositPerPerson: true,
393 description:
394 '<!-- Content --><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eget ac aliquam orci sed ac aliquet metus a. Magna dui consectetur tellus, suspendisse consequat, sem pulvinar. Ut tincidunt donec neque condimentum sed proin.</p>',
395 extras: [],
396 full: false,
397 fullPayment: true,
398 id: index,
399 initialEventEnd: null,
400 initialEventStart: null,
401 location: 'Location 1',
402 locationId: 6,
403 maxCapacity: 240,
404 maxCustomCapacity: null,
405 maxExtraPeople: 10,
406 name: item.name,
407 notifyParticipants: 0,
408 opened: true,
409 organizerId: 24,
410 parentId: null,
411 periods: [
412 {
413 bookings: [],
414 eventId: index,
415 googleCalendarEventId: null,
416 googleMeetUrl: '/',
417 id: index,
418 lessonSpace: '/',
419 outlookCalendarEventId: '/',
420 microsoftTeamsUrl: '/',
421 appleCalendarEventId: null,
422 periodEnd: `${moment()
423 .add(item.when + 1, 'days')
424 .format('YYYY-MM-DD')} 23:00:00`,
425 periodStart: `${moment().add(item.when, 'days').format('YYYY-MM-DD')} 03:00:00`,
426 zoomMeeting: {
427 joinUrl: '/',
428 },
429 },
430 {
431 bookings: [],
432 eventId: index,
433 googleCalendarEventId: null,
434 googleMeetUrl: '/',
435 id: index + 1,
436 lessonSpace: '/',
437 outlookCalendarEventId: '/',
438 microsoftTeamsUrl: '/',
439 appleCalendarEventId: null,
440 periodEnd: `${moment()
441 .add(item.when + 3, 'days')
442 .format('YYYY-MM-DD')} 23:00:00`,
443 periodStart: `${moment()
444 .add(item.when + 2, 'days')
445 .format('YYYY-MM-DD')} 03:00:00`,
446 zoomMeeting: {
447 joinUrl: '/',
448 },
449 },
450 {
451 bookings: [],
452 eventId: index,
453 googleCalendarEventId: null,
454 googleMeetUrl: '/',
455 id: index + 2,
456 lessonSpace: '/',
457 outlookCalendarEventId: '/',
458 microsoftTeamsUrl: '/',
459 appleCalendarEventId: null,
460 periodEnd: `${moment()
461 .add(item.when + 5, 'days')
462 .format('YYYY-MM-DD')} 23:00:00`,
463 periodStart: `${moment()
464 .add(item.when + 4, 'days')
465 .format('YYYY-MM-DD')} 03:00:00`,
466 zoomMeeting: {
467 joinUrl: '/',
468 },
469 },
470 ],
471 pictureFullPath: null,
472 pictureThumbPath: null,
473 places: 236,
474 position: null,
475 price: 10,
476 recurring: null,
477 show: true,
478 status: item.status,
479 tags: [
480 {
481 id: index,
482 eventId: index,
483 name: 'Tag 1',
484 },
485 {
486 id: index + 1,
487 eventId: index,
488 name: 'Tag 2',
489 },
490 ],
491 ticketRangeRec: 'calculate',
492 translations: null,
493 type: 'event',
494 upcoming: false,
495 zoomUserId: null,
496 }
497
498 dateGroupedEvents.value[moment().add(item.when, 'days').format('YYYY-MM-DD')].events.push(app)
499 })
500
501 // * Qr Codes
502 const qrCodesData = [
503 {
504 eventName: 'Event name',
505 type: 'groupTicket',
506 },
507 {
508 eventName: 'Event name',
509 type: 'ticket',
510 },
511 {
512 eventName: 'Event name',
513 type: 'ticket',
514 },
515 ]
516
517 // * Customize
518 const { amCustomize } = useReactiveCustomize()
519 let stepName = inject('stepName')
520
521 // * Cancel Event
522 let subStepName = inject('subStepName')
523
524 let cancelVisibility = computed(() => {
525 return subStepName.value === 'cancelEvent'
526 })
527
528 // * Colors
529 let amColors = inject('amColors')
530
531 let cssVars = computed(() => {
532 return {
533 '--am-c-cape-bgr': amColors.value.colorMainBgr,
534 '--am-c-cape-text': amColors.value.colorMainText,
535 '--am-c-cape-text-op70': useColorTransparency(amColors.value.colorMainText, 0.7),
536 '--am-c-cape-text-op25': useColorTransparency(amColors.value.colorMainText, 0.25),
537 '--am-c-cape-primary': amColors.value.colorPrimary,
538 }
539 })
540 </script>
541
542 <script>
543 export default {
544 name: 'CabinetEvents',
545 key: 'events',
546 }
547 </script>
548
549 <style lang="scss">
550 @mixin am-cabinet-events {
551 // cape -- cabinet panel events
552 .am-cape {
553 position: relative;
554
555 &__wrapper {
556 position: relative;
557 padding: 0 0 0 32px;
558
559 &:before {
560 content: '';
561 display: block;
562 width: 1px;
563 height: calc(100% - 4px);
564 position: absolute;
565 top: 4px;
566 left: 8px;
567 background-image:
568 linear-gradient(
569 180deg,
570 transparent,
571 transparent 50%,
572 var(--am-c-cape-bgr) 50%,
573 var(--am-c-cape-bgr) 100%
574 ),
575 linear-gradient(
576 180deg,
577 var(--am-c-cape-text-op25),
578 var(--am-c-cape-text-op25),
579 var(--am-c-cape-text-op25),
580 var(--am-c-cape-text-op25)
581 );
582 background-size:
583 3px 12px,
584 100% 20px;
585 }
586
587 &.am-no-border {
588 padding: 0;
589
590 &:before {
591 display: none;
592 }
593 }
594
595 &.am-rw-500 {
596 padding: 0;
597
598 &:before {
599 display: none;
600 }
601 }
602 }
603
604 &__date {
605 font-size: 15px;
606 font-weight: 500;
607 line-height: 1.6;
608 color: var(--am-c-cape-text-op70);
609 margin: 24px 0 8px;
610
611 &:before {
612 content: '';
613 display: block;
614 width: 16px;
615 height: 16px;
616 position: absolute;
617 top: 4px;
618 left: -32px;
619 background-color: var(--am-c-cape-text-op70);
620 border-radius: 50%;
621 z-index: 2;
622 }
623
624 &:after {
625 content: '';
626 display: block;
627 width: 16px;
628 height: 16px;
629 position: absolute;
630 top: 4px;
631 left: -32px;
632 background-color: var(--am-c-cape-bgr);
633 border-radius: 50%;
634 z-index: 1;
635 }
636
637 &.am-today {
638 &:before {
639 background-color: var(--am-c-cape-primary);
640 }
641 }
642
643 &.am-no-flag {
644 &:before,
645 &:after {
646 display: none;
647 }
648 }
649
650 &.am-rw-500 {
651 display: flex;
652 align-items: center;
653 &:before {
654 position: static;
655 margin-right: 8px;
656 }
657
658 &:after {
659 display: none;
660 }
661 }
662 }
663 }
664
665 .am-cap {
666 &__actions {
667 width: 100%;
668 display: flex;
669 justify-content: flex-end;
670 margin: 0 0 16px;
671 gap: 0 10px;
672
673 &.am-rw-420 {
674 justify-content: flex-start;
675 flex-wrap: wrap;
676 gap: 8px;
677
678 .am-button {
679 width: 100%;
680 }
681 }
682
683 .am-button {
684 .am-icon-plus,
685 .am-icon-scan-qr-code {
686 font-size: 24px;
687 }
688 }
689 }
690 }
691 }
692
693 #amelia-app-backend-new #amelia-container {
694 @include am-cabinet-events;
695 }
696 </style>
697